130 lines
5.7 KiB
JavaScript
130 lines
5.7 KiB
JavaScript
|
|
// © 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);
|
|||
|
|
}
|