gestion_table_tournante_libre/src/interface/plan/vue_plan.js

130 lines
5.7 KiB
JavaScript
Raw Normal View History

// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// La vue du plan côté interface (§ 7.2, § 8.5, § 13.3), sans DOM : ce que
// Plan.svelte décide de sa vue — à l'ouverture, après un changement de
// cadre, à la molette et aux boutons de zoom —, composé des seules
// fonctions de src/geometrie/vue.js. Le cadre est un paramètre, relu par
// l'appelant au moment du geste ; aucune fonction ne lit le DOM.
//
// Une vue ne se calcule que dans un cadre où kAjuste rend un nombre : un
// cadre dont la largeur ou la hauteur ne dépasse pas 2 × MARGE_AJUSTEMENT_PX,
// nul compris — un plan monté caché —, ne fixe aucune borne, et bornesZoom
// lèverait sur null. Chaque fonction fait ce contrôle avant de borner.
import { nomAffiche } from '../../application/personnes.js';
import { englobant, geometrieTable } from '../../geometrie/tables.js';
import { ajuster, borner, bornesZoom, facteurMolette, kAjuste, zoomerSous } from '../../geometrie/vue.js';
/** Facteur de zoom des boutons de zoom, appliqué autour du centre du cadre. */
export const FACTEUR_BOUTON = 1.25;
/**
* Le Cadre du contrat depuis un DOMRect : gauche ← left, haut ← top,
* largeur ← width, hauteur ← height. Plan.svelte, les gestes et les
* épreuves n'en lisent pas d'autre.
* @param {{left: number, top: number, width: number, height: number}} rect
* @returns {{gauche: number, haut: number, largeur: number, hauteur: number}}
*/
export function cadreDepuisRect(rect) {
return { gauche: rect.left, haut: rect.top, largeur: rect.width, hauteur: rect.height };
}
/**
* Vue à l'ouverture : le cadrage mémorisé, son k ramené dans les bornes du
* cadre (borner) ; sans cadrage, l'ajustement. Null dès que kAjuste vaut
* null, cadrage mémorisé ou non. Le cadrage reçu n'est pas modifié.
* @param {{k: number, tx: number, ty: number}|null} cadrage
* @param {{minX: number, minY: number, maxX: number, maxY: number}|null} englobantPlan
* @param {{gauche: number, haut: number, largeur: number, hauteur: number}} cadre
* @returns {{k: number, tx: number, ty: number}|null}
*/
export function vueInitiale(cadrage, englobantPlan, cadre) {
const k = kAjuste(englobantPlan, cadre);
if (k === null) return null;
if (cadrage === null) return ajuster(englobantPlan, cadre);
return borner(cadrage, cadre, bornesZoom(k));
}
/**
* Vue après un changement de cadre : k ajusté recalculé sur le nouveau
* cadre, k ramené dans ses bornes, le point de dessin au centre du cadre y
* restant. Quand kAjuste vaut null, la vue reçue elle-même, inchangée.
* @returns {{k: number, tx: number, ty: number}}
*/
export function vueApresCadre(vue, englobantPlan, cadre) {
const k = kAjuste(englobantPlan, cadre);
if (k === null) return vue;
return borner(vue, cadre, bornesZoom(k));
}
/**
* Vue après un cran de molette : zoom sous le pointeur (clientX, clientY),
* du facteur de facteurMolette(deltaY, deltaMode), aux bornes du cadre.
* Null quand il n'y a rien à zoomer : vue null, ou cadre où kAjuste vaut null.
* @param {{k: number, tx: number, ty: number}|null} vue
* @param {{clientX: number, clientY: number, deltaY: number, deltaMode: number}} evenement
* @returns {{k: number, tx: number, ty: number}|null}
*/
export function zoomerMolette(vue, englobantPlan, cadre, { clientX, clientY, deltaY, deltaMode }) {
const k = vue === null ? null : kAjuste(englobantPlan, cadre);
if (k === null) return null;
return zoomerSous(vue, cadre, clientX, clientY, facteurMolette(deltaY, deltaMode), bornesZoom(k));
}
/**
* Vue après « Zoom + » (sens 1) ou « Zoom moins » (sens -1) : zoom de FACTEUR_BOUTON,
* ou de son inverse, autour du centre du cadre, aux bornes du cadre. Null
* quand il n'y a rien à zoomer, comme zoomerMolette. RangeError pour un
* autre sens.
* @param {1|-1} sens
* @returns {{k: number, tx: number, ty: number}|null}
*/
export function zoomerBouton(vue, englobantPlan, cadre, sens) {
if (sens !== 1 && sens !== -1) throw new RangeError(`sens du zoom : 1 ou -1 attendu, reçu ${String(sens)}`);
const k = vue === null ? null : kAjuste(englobantPlan, cadre);
if (k === null) return null;
const facteur = sens === 1 ? FACTEUR_BOUTON : 1 / FACTEUR_BOUTON;
const x = cadre.gauche + cadre.largeur / 2;
const y = cadre.haut + cadre.hauteur / 2;
return zoomerSous(vue, cadre, x, y, facteur, bornesZoom(k));
}
/**
* L'englobant des emprises des tables d'un état des places (EtatPlaces,
* A5), chacune à sa capacité courante, comme le modèle de dessin les pose
* (geometrieTable) ; null sans table. Il ne dépend ni du tour ni de k.
* @returns {{minX: number, minY: number, maxX: number, maxY: number}|null}
*/
export function englobantDuTour(etat) {
return englobant(etat.tables.map((table) => geometrieTable(table, table.capacite)));
}
/**
* Les tours que le sélecteur offre : 1 à tours, croissants.
* @param {number} tours entier ≥ 1
* @returns {number[]}
*/
export function toursOfferts(tours) {
return Array.from({ length: tours }, (_, rang) => rang + 1);
}
/**
* Le tour que montre le plan : le tour demandé, ramené au dernier quand le
* nombre de tours a baissé depuis la demande — etatPlaces lève au-delà.
* @param {number} tour entier ≥ 1 @param {number} tours entier ≥ 1
* @returns {number}
*/
export function tourAffiche(tour, tours) {
return Math.min(tour, tours);
}
/**
* La fonction nomDe du modèle de dessin : l'identifiant d'un participant de
* la charge vers son nom affiché (nomAffiche, F1).
* @returns {(id: number) => string}
*/
export function nomDeCharge(charge) {
const noms = new Map(charge.participants.map((participant) => [participant.id, nomAffiche(participant)]));
return (id) => noms.get(id);
}