/**
* Чистая логика курсора вертикального списка: без Pixi, тестируется изолированно.
* MenuList использует её для клавиатурной навигации; предикат выбираемости
* пропускает недоступные пункты (disabled).
*/
export class ListCursor {
/** Индекс выбранного элемента. */
index = 0;
constructor(
private count: number,
private selectable: (index: number) => boolean = () => true
) {}
/** Изменить количество элементов (список пересобран); курсор зажимается. */
setCount(count: number): void {
this.count = count;
if (this.index >= count) {
this.index = Math.max(0, count - 1);
}
if (!this.selectable(this.index)) this.move(1);
}
/** Сдвиг на |delta| шагов; каждый шаг — к соседнему выбираемому элементу. */
move(delta: number): void {
if (this.count === 0) return;
const dir = Math.sign(delta);
let steps = Math.abs(delta);
while (steps > 0) {
let landed = false;
for (let i = 1; i <= this.count; i++) {
const next = (((this.index + dir * i) % this.count) + this.count) % this.count;
if (this.selectable(next)) {
this.index = next;
landed = true;
break;
}
}
if (!landed) return; // все элементы недоступны — курсор стоит
steps--;
}
}
/** Курсор в начало/конец. */
home(): void {
this.index = 0;
}
end(): void {
if (this.count > 0) this.index = this.count - 1;
}
/** Есть ли элементы. */
get empty(): boolean {
return this.count === 0;
}
}