Newer
Older
rpg / packages / engine / src / ui / listCursor.ts
/**
 * Чистая логика курсора вертикального списка: без 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;
    }
}