Funpix Kit
Inicio / Componentes / fpx · inventory_ui

fpx · inventory_ui

La ventana de inventario del ejemplar: ItemSlot, ficha rica con comparativa (ItemTooltip), marco de rareza, menú contextual con acciones inyectadas e InventoryPanel con bind().

El ZIP incluye: addons/fpx/inventory_ui, addons/fpx/items, addons/fpx/inventory, addons/fpx/stats, addons/fpx/quests, addons/fpx/ui_kit + sus tests + data/balance/default.tres.

Qué es

La ventana de inventario del ejemplar (pases v2→v8, referencia Dofus), portable: ItemSlot (hueco cuadrado con icono/iniciales + badge de stack), ItemTooltip (la ficha del mockup del diseñador 2026-08-31: header a dos columnas con «Nivel ♦ Tipo» en dorado y línea de set, banner de aviso condicional — que absorbe las restricciones ED-1 y el campo warning —, características de arma + EFECTOS DEL ARMA (campos weapon_*), headers de sección edge-to-edge ESTADO (campo status)/EFECTOS/EFECTO/CONDICIONES, filas de efecto icono SVG + valor coloreado por signo + etiqueta blanca, divisoria con puntito, flavor en itálica entre « », pie con badges de tecla completo/reducido por tipo; comparativa Δ contra el paperdoll que el llamador inyecta, oculta hasta pulsar \; sin precio por decisión de diseño; la rareza viste SOLO el borde de la ficha con su color — común = borde neutro, decisión 2026-08-31), ItemFormat (formateo puro de líneas), RarityFrame (marco/patrón por rango — la ficha YA NO lo monta; queda disponible para otras UIs), ItemContextMenu (menú por tipo con acciones inyectadas), InventoryPanel (paperdoll en dos raíles según el EquipmentLayout, catálogo paginado con búsqueda/categoría/orden, drag&drop, doble click, stock en vivo; se enlaza a cualquier proveedor con bind()).

Instalación

  1. Descarga fpx-inventory_ui-v0.2.0.zip (botón de arriba) y extráelo en la raíz de tu proyecto Godot — deja la estructura tal cual:
text
tu-juego/
├── project.godot
├── addons/
│   └── fpx/
│       ├── inventory_ui/
│       ├── items/
│       ├── inventory/
│       ├── stats/
│       ├── quests/
│       └── ui_kit/
└── tests/          ← opcional: bórralo si no quieres los tests
  1. Abre el proyecto (o corre --import headless). Sin plugins ni autoloads que activar: las clases quedan disponibles en cualquier script.
  2. Opcional — verifica con la suite (necesita GdUnit4, ver la guía): -a tests/inventory_ui.

Ejemplo de uso

Usa las fixtures incluidas en el componente para probar en segundos; en tu juego, sustitúyelas por tus datos.

inventory_ui · ejemplo
# El panel del ejemplar, enlazado a TU personaje (o a la fixture, como aquí).
# Sin bind(), un panel es una ventana vacía: mochila, paperdoll, oro, figura
# y mutaciones son del PROVEEDOR.
var src := InventoryUiFixtures.provider()   # {inventory, equipment, wallet, actions}
var panel := InventoryPanel.new()
panel.bind(src["inventory"], src["equipment"], null, src["actions"])
add_child(panel)

panel.toggle()                    # abrir/cerrar (o cablea toggle_key_matcher)
panel.item_activated.connect(func(item: ItemDefinition) -> void:
	print("doble click en ", item.id))
# tras cualquier mutación del inventario desde fuera:
panel.refresh_if_open()

# Piezas sueltas — cada una funciona por separado:
var slot := ItemSlot.make(espada, 1, Vector2(56, 56))   # hueco con icono + badge
add_child(slot)

# Ficha rica al hacer hover (mockup del diseñador 2026-08-31: banner de aviso,
# ESTADO, bloques de arma, EFECTOS con icono + valor por signo, pie con badges).
# La comparativa Δ contra el paperdoll inyectado nace oculta: `\` la conmuta.
ItemTooltip.attach(slot, espada, mi_equipment)

# La línea de set del header se resuelve con los sets INYECTADOS (una vez):
ItemTooltip.set_source = SetCatalog.load_all()

# …y la ficha también se puede EMBEBER (previews, editores), sin capa ni hover:
var ficha := ItemTooltip.make(espada, mi_equipment)
add_child(ficha)

# Menú contextual por tipo de ítem, con las acciones INYECTADAS:
slot.pressed.connect(func() -> void:
	ItemContextMenu.open(self, espada, get_global_mouse_position(), false, inv, {
		&"equip": func(item: ItemDefinition, target: StringName) -> StringName:
			return InventoryOps.equip_from_inventory(inv, eq, item, target)["reason"],
		&"destroy": func(item: ItemDefinition) -> int:
			return InventoryOps.destroy_item(inv, item),
	}))

# Formateo puro para tus propias UIs:
print(ItemFormat.effect_parts(espada))    # icono + valor + etiqueta (mockup)
print(ItemFormat.weapon_damage_parts(espada))  # «26 a 32 de daños de agua»
print(ItemFormat.thousands(1234567))      # "1.234.567"

Demo en vivo

El componente REAL corriendo en tu navegador — Godot 4.7 exportado a Web sobre las fixtures del kit. La primera carga baja el motor (~10 MB comprimido) y se comparte entre todas las demos.

abrir a pantalla completa ↗

API pública

ClaseQué da
ItemSlotmake(item, count, size) -> Button · paint(button, item, count) · attach_stack_badge(button, count)
ItemTooltipattach(control, item, compare_with: Equipment = null) · show_for(owner, item, compare_with) · make(item, compare_with) -> ItemTooltip (embebible, sin capa) · dismiss() · set_source: Array[SetDefinition] (estático; el juego lo inyecta una vez, p.ej. SetCatalog.load_all()) · stat_icon(shape, color, px)
ItemFormateffect_lines, effect_parts (icono+valor+etiqueta del mockup), effects_title (EFECTO singular), modifier_icon/MODIFIER_ICONS, set_of, status_lines/warning_lines (campos status/warning del esquema + ED-1), weapon_trait_parts/weapon_crit_bonus_line/weapon_damage_parts (WEAPON_ELEMENTS), has_full_footer, comparison_lines, condition_lines, binding_lines, type_label, rarity_*, abbreviation, thousands, modifier_line
RarityFramemake(kind, item); BORDER_WIDTH, CORNER_RADIUS
ItemContextMenuopen(host, item, at, locked, inventory, actions) -> ContextView · actions: &"equip", &"use", &"destroy"(item)->int, &"destroyed"(count)
InventoryPanel (CanvasLayer)bind(inventory\|Callable, equipment\|Callable, layout = null, actions = {}) · toggle()/is_open()/refresh()/refresh_if_open() · combat_locked · toggle_key_matcher: Callable(InputEventKey) -> bool · default_binder (estático; el juego lo registra una vez) · señal item_activated(item)

actions del panel (catálogo tolerante): &"equip"(item, target) -> motivo, &"unequip"(slot) -> motivo, &"destroy"(item) -> int, &"gold"() -> int, &"figure"() -> String, &"sets"() -> Array[Dictionary].

Firmas completas (del código)

InventoryPanelextends CanvasLayerinventory_panel.gd

Inventario y equipo (E1-INV-01/02 + pases E1-UX-03 v2→v5, referencia de Dofus): paperdoll con los 9 slots de equipo en dos raíles alrededor del personaje, Astralitas (stub del GDD) y conjuntos (placeholder); a la derecha el catálogo con búsqueda/categoría/orden, PÁGINAS de 50 huecos con scroll (v5) y el oro. Fichas ricas al hover y stock EN VIVO. Reglas v5→v8 (pedido del diseñador): los huecos JAMÁS desaparecen — al FILTRAR (búsqueda o categoría del desplegable) los resultados se COMPACTAN desde el hueco 1 y lo que no coincide se oculta (v8: sin rail de botones, el desplegable manda); el EQUIPO se equipa/desequipa por DOBLE click o drag&drop entre catálogo y paperdoll — y el drop solo entra en el hueco CORRECTO de la pieza (v6); los CONSUMIBLES se usan por DOBLE click o «Utilizar» del menú contextual (v7: el click simple ya no consume); el CLICK DERECHO abre el menú contextual por tipo calcado de Dofus (ItemContextMenu compartido con la barra de objetos, v8); los stackeables se arrastran SOLO a la barra de objetos (la hotbar de hechizos no los acepta). En combate la ventana es 100 % consulta (P-28). La tecla de Keymap &"open_inventory" (I) abre/cierra.

const SLOT_LABELS: Dictionary[StringName, String] = …
const SLOT_SHORT: Dictionary[StringName, String] = …

Texto compacto del slot VACÍO en el paperdoll (greybox hasta E1-ART).

const DOLL_LEFT: Array[StringName] = [&"helmet", &"armor", &"main_hand", &"off_hand"]

Raíles del paperdoll (izquierda/derecha del personaje, como la referencia).

const DOLL_RIGHT: Array[StringName] = [&"necklace", &"ring_1", &"ring_2", &"belt", &"boots"]
const REASONS: Dictionary[StringName, String] = …
const COLUMNS: int = 5
const SLOT_SIZE := Vector2(48.0, 48.0)
const PAGE_SIZE: int = 50

Paginación v5 (pedido del diseñador): 50 huecos por página + scroll.

const CATEGORY_FILTERS: Array[Array] = …

Filtro por categoría del catálogo: &"" = todas.

const SORT_MODES: Array[Array] = …

Orden del catálogo: &"slots" = los 20 huecos tal cual (con vacíos).

var combat_locked: bool = false

P-28 (sistemas_core §15.6) + v9: en combate NADA se cambia ni se consume (la ventana es consulta pura); la escena de combate lo activa.

var toggle_key_matcher: Callable = Callable()

Tecla de abrir/cerrar: Callable(InputEventKey) -> bool que INYECTA el juego (en el ejemplar, Keymap.matches(&"open_inventory", key)); sin ella, solo ESC.

func bind(inventory: Variant, equipment: Variant, layout: EquipmentLayout = null, actions: Dictionary = {}) -> void

Enlaza el panel a su fuente de datos (ver cabecera). layout null = el paperdoll por defecto del componente inventory.

func is_open() -> bool
func toggle() -> void
func refresh_if_open() -> void
func refresh() -> void
ItemContextMenuextends RefCounteditem_context_menu.gd

Menú contextual de ÍTEM (v7→v8, calcado de Dofus por tipo) — COMPARTIDO: el inventario y la barra de objetos abren EL MISMO menú. Funcionales: Equipar (si el llamador lo cablea — la barra de objetos no equipa), Utilizar y «Destruir el objeto» (TODO el stock, confirmación en submenú, GameState.destroy_item avisa por player_inventory_changed); el resto son placeholders declarados que se cablean con su sistema (P-43). locked = combate (P-28): lo funcional se muestra apagado, el menú sigue siendo consulta.

static func open(host: Node, item: ItemDefinition, at: Vector2, locked: bool, inventory: Inventory, actions: Dictionary) -> ContextView

FK-7 (Funpix Kit): el menú NO conoce al personaje del juego. Recibe el inventory (stock para «Destruir ×N»; null = 0) y un catálogo TOLERANTE de acciones: {&"equip": Callable(), &"use": Callable(), &"destroy": Callable(item) -> int, &"destroyed": Callable(count)}. Acción ausente o inválida = entrada apagada (equip/use) u oculta (destroy).

ItemFormatextends RefCounteditem_format.gd

Formateo PURO de ItemDefinition para las fichas ricas (E1-UX-03, referencia visual de Dofus): traduce modifiers (vocabulario GearBonus.FIELD_KEYS + atributos §2) a líneas legibles con signo, y los metadatos (tipo, condiciones §8, peso, precio) a texto. Sin nodos: testeable headless; ItemTooltip solo pinta lo que sale de aquí.

const SLOT_TYPE_LABELS: Dictionary[StringName, String] = …
const MODIFIER_LABELS: Dictionary[StringName, String] = …

Etiquetas de los bonos §3 (GearBonus.FIELD_KEYS) y atributos §2 — en minúscula, estilo "341 vitalidad" de la referencia.

const RARITIES: Dictionary[StringName, Dictionary] = …

Rarezas del catálogo v2 (P-56 Fase B; escalafón del diseñador 2026-07-19: normal > poco común > raro > épico > legendario > mítico > angelical > trascendente). Cada tier: etiqueta, color propio del tema (UiThemeDefinition §Rareza de ítems) y rank — el peldaño del escalafón que gradúa los ornamentos del marco (RarityFrame). El ORDEN del diccionario ES el escalafón. &"comun" sigue sin teñir el nombre — compat con lo existente.

static func rarity_label(item: ItemDefinition) -> String
static func rarity_color_key(item: ItemDefinition) -> StringName

Clave de color semántico (UiTheme.color) de la rareza del ítem.

static func rarity_rank(item: ItemDefinition) -> int

Peldaño del escalafón (0=común … 7=trascendente): decide cuánta filigrana dibuja el marco de la ficha. Rareza desconocida cae al peldaño 0.

static func type_label(item: ItemDefinition) -> String

Línea de tipo de la cabecera: "Arma", "Botas", "Consumible", "Recurso"…

static func effect_lines(item: ItemDefinition) -> Array[Dictionary]

Líneas de EFECTOS: [{"text": "+20 ATK de arma", "positive": bool}]. Para consumibles es su efecto (v1: heal, P-27); los bonos a cero se omiten.

const MODIFIER_ICONS: Dictionary[StringName, Dictionary] = …

--- Ficha mockup 2026-08-31 (DELTA kit-first, ver SYNC.md) ---------------- Icono de stat por clave de modifier (catálogo §4 del addendum del mockup): {shape, color} — shape es un glifo del catálogo SVG de ItemTooltip y color una clave semántica del tema. Placeholders procedurales hasta que exista arte (plan §4.4); clave desconocida cae a punto neutro.

static func modifier_icon(key: StringName) -> Dictionary
static func effect_parts(item: ItemDefinition) -> Array[Dictionary]

Filas de efectos DESGLOSADAS para la ficha mockup: icono + valor + etiqueta, de modo que ItemTooltip coloree SOLO el valor (regla del mockup: valor en success/danger según signo, etiqueta en blanco). El valor va sin «+» (los positivos se leen por el color, como en la referencia); los negativos llevan «-». Consumibles: una fila sin valor (batería verde + texto del efecto).

static func effects_title(item: ItemDefinition, count: int) -> String

Header de la sección de efectos: EFECTO en singular para no-equipo con un único efecto (consumibles/contenedores de la referencia), EFECTOS si no.

static func set_of(item: ItemDefinition, sets: Array[SetDefinition]) -> SetDefinition

Set al que pertenece la pieza, buscándola por id en los sets INYECTADOS (SetCatalog.load_all() en el ejemplar; la ficha no escanea disco). null si ninguna lo lista o el ítem no es equipo.

static func status_lines(item: ItemDefinition) -> Array[String]

Líneas de ESTADO de la ficha (forjamagia, nivel de mascota… del addendum): el campo status del esquema, saneado (vacías fuera). Vacío = sin sección.

static func warning_lines(item: ItemDefinition) -> Array[String]

Líneas del BANNER DE AVISO (bloque condicional del mockup, disponible para todos los tipos): el aviso libre del esquema (warning) + las restricciones de vínculo/economía ED-1 — que en la ficha mockup dejan de tener sección propia «VÍNCULO» y suben al banner.

const WEAPON_ELEMENTS: Dictionary[StringName, Dictionary] = …

Elementos del daño de arma (EFECTOS DEL ARMA): etiqueta de la línea + icono del catálogo §4. Elemento desconocido cae a etiqueta genérica con su nombre.

static func weapon_trait_parts(item: ItemDefinition) -> Array[Dictionary]

Características del arma (bloque sin header, addendum §1.2): filas {label, value, icon, icon_color} de las líneas con dato (> 0), en el orden de la referencia. El «Bonus de críticos» NO va aquí: es la columna derecha (weapon_crit_bonus_line). Vacío = el bloque se colapsa.

static func weapon_crit_bonus_line(item: ItemDefinition) -> String

Columna derecha del bloque de arma: "Bonus de críticos +N" ("" si no hay).

static func weapon_damage_parts(item: ItemDefinition) -> Array[Dictionary]

Línea(s) de la sección EFECTOS DEL ARMA: mismo desglose que effect_parts (icono + rango en bold + etiqueta blanca). Sin daño (max <= 0) = sin sección.

static func has_full_footer(item: ItemDefinition) -> bool

Footer COMPLETO (3 filas con Ctrl teóricos y \ comparar) solo para lo equipable/comparable; el resto lleva el reducido (fila de Opciones + Alt).

static func modifier_line(key: StringName, value: float) -> Dictionary

Una línea de bono con signo: "+12 % de crítico" / "-10 iniciativa".

static func comparison_lines(item: ItemDefinition, equipped: ItemDefinition) -> Array[Dictionary]

Ficha COMPARATIVA (UI-2, P-43 · P-56 §7.2): Δ de stats del ítem frente a la pieza equipada del MISMO slot. Devuelve [{"text": "+5 PV", "sign": 1|0|-1}] recorriendo el vocabulario §3 (GearBonus.FIELD_KEYS) y luego los atributos §2 (StatBlock.ATTRS). REUSA GearBonus para el diff — no reinventa el cálculo de stats (mismo agregado que entra al pipeline de StatBlock). equipped == null (slot vacío) → el ítem es todo-positivo: cada bono suyo se muestra como ganancia; un stat que solo tiene lo equipado sale como pérdida; delta 0 con ambos presentes = "=".

static func delta_line(key: StringName, delta: float) -> Dictionary

Una línea de diff con signo: "+5 PV" (verde), "-2 iniciativa" (rojo) o "= % de crítico" (sin cambio). sign es 1/-1/0 para que el tooltip elija el color semántico (success/danger/text_muted).

static func consumable_effect(item: ItemDefinition) -> String

Efecto del consumible (v1: curación y otorga-misión; crece en F3, P-27).

static func condition_lines(item: ItemDefinition) -> Array[String]

Condiciones §8 para equipar (vacío = sin requisitos que mostrar).

static func binding_lines(item: ItemDefinition) -> Array[String]

Líneas de VÍNCULO y economía de la ficha (ED-1): ligadura, unicidad y no-venta — mismo estilo que condition_lines (vacío = nada que mostrar).

static func thousands(value: int) -> String

Miles con puntos ("2.157.835") para oro y precios.

static func abbreviation(item: ItemDefinition) -> String

Iniciales para slots SIN icono (greybox hasta E1-ART, P-34): "Espada de recluta" → "ER", "Chatarra" → "Ch".

static func initials(name: String) -> String

Iniciales CRUDAS de un nombre cualquiera (slots de hotbar y fichas de skill): "Golpe Certero" → "GC", "Tajo" → "TA".

ItemSlotextends RefCounteditem_slot.gd

Hueco cuadrado de ÍTEM compartido (C3, pedido del diseñador 2026-08-04: «los objetos dropeados deben previsualizarse como en el inventario — el icono miniatura también debe aparecer con su stack si tiene»). Es EXACTAMENTE el pintado del catálogo del inventario, extraído aquí para no duplicarlo: · icono miniatura si el ítem tiene arte, iniciales si no (P-34); · contador de stack abajo a la derecha cuando hay más de uno (HotbarSlot.attach_stack_badge — el MISMO badge de la barra de objetos); · ficha RICA al hover (ItemTooltip, con su marco/patrón de rareza P-56). paint sirve al inventario (que ya trae su botón con drag&drop y menús cableados) y make a las vistas de SOLO LECTURA — la pantalla de victoria. Sin nodos propios ni estado: testeable headless.

const DEFAULT_SIZE := Vector2(48.0, 48.0)

Tamaño del hueco del catálogo del inventario (InventoryPanel.SLOT_SIZE).

const ICON_INSET: float = 4.0

Margen del icono dentro del hueco: el arte no toca el marco del slot.

static func paint(button: Button, item: ItemDefinition, count: int) -> void

Pinta el CONTENIDO de un botón-hueco YA creado (el llamador pone el skin, el tamaño y la interacción). Idempotencia: no limpia — se llama sobre botones recién construidos, como en el refresh del inventario.

static func make(item: ItemDefinition, count: int, slot_size: Vector2 = DEFAULT_SIZE) -> Button

Hueco COMPLETO de solo lectura: skin de slot, tamaño, pintado y ficha rica cableada al hover. item == null devuelve el hueco vacío ("·", inerte).

static func attach_stack_badge(button: Button, count: int) -> void

Contador de stack en MINIATURA (abajo a la derecha): el MISMO look en el inventario, el botín y la barra de objetos (FK-7: antes vivía en HotbarSlot; la inversión de dependencia se corrige — la barra lo llama de aquí).

ItemTooltipextends PanelContaineritem_tooltip.gd

Ficha flotante RICA de un ítem (E1-UX-03), al mockup del diseñador 2026-08-31 (spec tooltip + addendum de variantes, referencia Dofus): cabecera a dos columnas (nombre / «Nivel N ♦ Tipo» en dorado / línea de set) con el arte a la derecha, banner de aviso condicional, headers de sección edge-to-edge (EFECTOS/EFECTO, ESTADO, CONDICIONES), filas de efecto icono + VALOR coloreado por signo + etiqueta blanca, divisoria con puntito, flavor en itálica entre « » y pie de atajos con badges de tecla (completo para equipo, reducido para el resto). El bloque comercial (peso/precio) queda FUERA por decisión de diseño; las restricciones ED-1 (ligadura/único/ no vendible) suben al banner de aviso. La RAREZA viste SOLO el borde de la ficha con su color (decisión del diseñador 2026-08-31: fuera el patrón diagonal y la filigrana de RarityFrame); común = borde neutro del mockup. Como las fichas de ContextView, ignora el ratón y vive en su propio CanvasLayer (bajo los contextuales); attach() la cablea al hover de cualquier Control. Solo hay UNA viva a la vez: el hover manda. La COMPARATIVA (UI-2, P-43 · P-56 §7.2) ya no es banda fija: se construye oculta y la tecla \ del pie la muestra/esconde (el resto de atajos — Alt fijar, Ctrl teóricos — sigue PLACEHOLDER declarado, P-43).

const LAYER_TOOLTIP: int = 93 # bajo ContextView (94/95)
const CONTENT_WIDTH: float = 340.0

Ancho total de la ficha (mockup ~558 px de captura → ~340 a resolución de juego; plan de adaptación §4.1).

const H_PAD: int = 14

FIX-F evolucionado (mockup): las bandas de sección van EDGE-TO-EDGE, así que el aire lateral ya no lo pone el stylebox del panel sino el margen de cada bloque de texto. El stylebox del panel solo reserva el ancho del borde.

const ROW_WIDTH: float = CONTENT_WIDTH - 2.0 * RarityFrame.BORDER_WIDTH - 2.0 * H_PAD

Ancho útil de una fila de texto: CONTENT_WIDTH - borde (2×2) - H_PAD (2×14). Los labels con autowrap lo llevan como ancho EXPLÍCITO — sin él, su altura mínima se infla antes del primer layout y reset_size() deja el panel gigante.

const ICON_SHAPES: Dictionary[StringName, String] = …

Glifos SVG 16×16 de los iconos de stat (catálogo §4 del addendum del mockup) — placeholders procedurales desde los colores semánticos hasta que exista arte (plan §4.4). {f} = fill, {s} = stroke del color del icono.

static func attach(control: Control, item: ItemDefinition, compare_with: Equipment = null) -> void

Cablea la ficha al hover de control. Los grids del inventario se reconstruyen enteros en cada refresh, así que basta conectar al crearlos; tree_exiting cubre el caso "el botón murió con el cursor encima".

static func show_for(owner_control: Control, item: ItemDefinition, compare_with: Equipment = null) -> void

Muestra la ficha pegada al Control (a su derecha; si no cabe, refleja a la izquierda). Descartar la anterior evita fichas huérfanas al mover rápido.

static func make(item: ItemDefinition, compare_with: Equipment = null) -> ItemTooltip

Construye la ficha SIN capa ni anclaje, para EMBEBERLA en un layout (una preview fija, la demo del kit): el llamador la añade donde quiera.

static func dismiss() -> void
static func stat_icon(shape: StringName, color: Color, px: int = 15) -> Texture2D

Icono de stat procedural (SVG desde el color pedido), cacheado por forma+color+tamaño. Forma desconocida cae al punto neutro.

ItemTooltip.new()

Constructor.

RarityFrameextends Controlrarity_frame.gd

Marco y fondo PROCEDURALES de rareza para la ficha de ítem (P-56 Fase B v2, lámina de marcos del diseñador 2026-07-19): cero assets — todo se dibuja con el color del tier (UiThemeDefinition §Rareza de ítems vía UiTheme). Dos capas, ambas hermanas del contenido dentro del PanelContainer: BACKDROP (primer hijo) — patrón de diagonales sutil que tiñe TODO el fondo de la ficha; el icono queda limpio porque va sobre banda sólida (ItemTooltip._add_header), no porque aquí se recorte. BORDER (último hijo) — filigrana sobre el contenido: esquineras, línea interior, rombos laterales y corona superior; cuánta aparece lo decide el peldaño del escalafón (ItemFormat.rarity_rank). Ambas capas dibujan respecto al RECT DEL PANEL padre (no al content-rect que les asigna el contenedor): así la filigrana abraza el borde real.

const CORNER_RADIUS: int = 4

Radio de esquina del marco de rareza (ItemTooltip lo aplica al stylebox): debe ser <= PATTERN_INSET para que ninguna diagonal del patrón asome por fuera de la esquina redondeada del panel.

const BORDER_WIDTH: int = 2
const PATTERN_INSET := 4.0
const PATTERN_SPACING := 10.0
const PATTERN_ALPHA := 0.08
const BRACKET_INSET := 5.0
const BRACKET_LENGTH := 13.0
var kind := Layer.BACKDROP
var tint := Color.WHITE
var rank: int = 0
static func make(kind_value: Layer, item: ItemDefinition) -> RarityFrame
RarityFrame.new()

Constructor.

Qué configura el juego

A qué se enlaza el panel (su inventario/paperdoll, o Callables que los devuelven si reemplaza al personaje), sus mutaciones (en el ejemplar GameState.equip_item/unequip_slot/destroy_item), quién dispara refresh_if_open() al cambiar el inventario (su bus de eventos), la tecla de abrir (toggle_key_matcher) y su tema (ui_kit). Sin bind(), un panel es una ventana vacía.

Fixture mínima

testing/inventory_ui_fixtures.gd — InventoryUiFixtures.provider() (mochila + paperdoll + oro + acciones sobre InventoryOps) y bound_panel().

inventory_ui_fixtures.gdcódigo completo
inventory_ui_fixtures.gd
# res://addons/fpx/inventory_ui/testing/inventory_ui_fixtures.gd
class_name InventoryUiFixtures
extends RefCounted
## Fixture mínima del componente `inventory_ui`: un panel de inventario
## ENLAZADO a la mochila y el paperdoll de `InventoryFixtures`, con oro y
## acciones en memoria (equipar/desequipar/destruir vía `InventoryOps`) — el
## «hola mundo» del panel sin ningún personaje del juego.


## Proveedor en memoria: {inventory, equipment, gold, actions}.
static func provider(inventory: Inventory = null, equipment: Equipment = null,
		gold: int = 1234) -> Dictionary:
	var inv := inventory if inventory != null else InventoryFixtures.starter_bag()
	var eq := equipment if equipment != null else InventoryFixtures.canonical_paperdoll()
	var wallet := {"gold": gold}
	var actions := {
		&"equip": func(item: ItemDefinition, target: StringName) -> StringName:
			return InventoryOps.equip_from_inventory(inv, eq, item, target)["reason"],
		&"unequip": func(slot: StringName) -> StringName:
			return InventoryOps.unequip_to_inventory(inv, eq, slot),
		&"destroy": func(item: ItemDefinition) -> int:
			return InventoryOps.destroy_item(inv, item),
		&"gold": func() -> int: return int(wallet["gold"]),
		&"figure": func() -> String: return "Recluta\nnivel 5",
		&"sets": func() -> Array[Dictionary]: return [],
	}
	return {"inventory": inv, "equipment": eq, "wallet": wallet, "actions": actions}


## Panel ya enlazado al proveedor (sin añadir al árbol: eso lo hace el test).
static func bound_panel(source: Dictionary = {}) -> InventoryPanel:
	var src := source if not source.is_empty() else provider()
	var panel := InventoryPanel.new()
	panel.bind(src["inventory"], src["equipment"], null, src["actions"])
	return panel

Cómo se prueba

tests/inventory_ui/ — item_format_test (11 casos del ejemplar, 1:1), item_tooltip_test (la ficha mockup: desglose icono/valor/etiqueta, EFECTO singular, set por id, estado/aviso/arma desde el esquema, orden de bandas sin precio, pie completo/reducido, \ conmuta la comparativa, variaciones del builder), inventory_panel_test (paperdoll + paginación, filtro fantasma, equipar por acción inyectada, ficha de rareza, menú contextual con acciones), sobre el preset dark de ui_kit.

Ejemplar oro (Stremus)

scripts/ui/{item_slot,item_tooltip,item_format,rarity_frame,item_context_menu}.gd + scenes/ui/inventory_panel.gd (main@670d5a0); la glue es scripts/ui/inventory_panel_glue.gd (registrada desde GameState), que se queda allí.

Deuda declarada

  • FLAG-UI-PLACEHOLDERS (P-43): Alt (fijar) y Ctrl (teóricos) del pie siguen placeholder visual (\ sí conmuta la comparativa); entradas «Probar/Recetas/Bestiario…» del menú siguen placeholder.
  • Los iconos de stat son placeholders SVG procedurales (catálogo §4 del addendum) hasta que exista arte.
  • El daño de arma del esquema es UNA línea (min/max/elemento); armas multi-elemento (Dagoteras) pedirán una lista cuando haya contenido que la necesite.
  • FLAG-UI-ASTRALITAS: el stub de 6 huecos es del juego (sigue pintado en el panel).
  • FLAG-I18N (D-FW-8): etiquetas ES (SLOT_LABELS, SLOT_SHORT, REASONS, CATEGORY_FILTERS, textos del menú) siguen en código.
  • Hotbar/HotbarSlot (barra de objetos) no viajan aún (dependen de GameState).
Generado el 2026-08-31 · Funpix Kit — componentes extraídos de Stremus: El Tercer Despertar (ejemplar oro) · Funpixart