using System;
using System.Collections.Generic;
using MyInfoMate.Vr.Ui;
using UnityEngine;
namespace MyInfoMate.Vr.Menu
{
///
/// Le choix de la langue, posé avant le menu.
///
/// Pourquoi le demander, et pourquoi à chaque visite. Un casque en borne passe
/// de main en main : la langue n'est pas un réglage de l'appareil, c'est une propriété
/// du visiteur en cours. Elle vit donc le temps d'une visite et rien de plus — rien
/// n'est écrit sur le disque, et le visiteur suivant est de nouveau accueilli par cet
/// écran. C'est la même logique que la session de statistiques (voir
/// ), et pour la même raison.
///
/// Changer de langue ne coûte aucun appel réseau : l'export porte toutes les
/// traductions d'un coup — c'est explicitement l'arbitrage de
/// ConfigurationExport.FetchAsync. Le menu se reconstruit simplement avec
/// l'autre langue, y compris hors ligne.
///
/// Ce sont les mêmes tuiles que le menu, avec un drapeau à la place de la
/// photo de section : c'est le premier écran de la visite, et il doit annoncer la
/// grammaire de ce qui suit plutôt que d'être un formulaire posé devant. Les drapeaux
/// sont ceux de l'app mobile, à l'identique.
///
/// L'écran ne s'affiche pas quand la visite n'est offerte que dans une langue : un
/// choix à une possibilité n'est pas un choix, c'est un écran de plus avant le contenu.
///
public class LanguagePicker : MonoBehaviour
{
const float RadiusMeters = 2.2f;
/// Hauteur du centre de la rangée de tuiles.
const float HeightMeters = 1.35f;
/// Le même écart que la grille du menu, pour la même raison.
const float GapMeters = 0.03f;
static float StepDegrees =>
(MenuItemPanel.WidthMeters + GapMeters) / RadiusMeters * Mathf.Rad2Deg;
/// Le code de la langue choisie, tel qu'il part dans les traductions.
public event Action Chosen;
public static LanguagePicker Create(Transform head, IList languages)
{
var root = new GameObject("LanguagePicker");
var picker = root.AddComponent();
if (head != null)
{
var forward = head.forward;
forward.y = 0f;
if (forward.sqrMagnitude < 0.01f) forward = Vector3.forward;
root.transform.position = new Vector3(head.position.x, 0f, head.position.z);
root.transform.rotation = Quaternion.LookRotation(forward.normalized);
}
var selector = root.AddComponent();
selector.Configure(head);
selector.Selected += picker.OnSelected;
picker.Build(languages);
return picker;
}
void Build(IList languages)
{
// La consigne est écrite dans chacune des langues proposées : celui
// qui ne lit pas le français doit pouvoir comprendre l'écran qui lui demande
// s'il lit le français.
var prompts = new List();
foreach (var language in languages)
{
var prompt = Strings.Get(Strings.ChooseLanguage, language);
if (!prompts.Contains(prompt)) prompts.Add(prompt);
}
var heading = Label.Create(transform, "Heading", Label.Anchor.MiddleCenter,
0.075f, Color.white);
heading.Text = string.Join("\n", prompts);
heading.transform.localPosition = new Vector3(
0f,
HeightMeters + MenuItemPanel.HeightMeters * 0.5f + 0.13f + prompts.Count * 0.055f,
RadiusMeters);
for (var i = 0; i < languages.Count; i++)
{
var panel = MenuItemPanel.Create(
languages[i], Strings.NameOf(languages[i]), transform);
// Une langue sans drapeau garde la tuile nue, avec son nom au centre :
// c'est déjà lisible, et c'est ce que fait une tuile de section sans photo.
var flag = Flag(languages[i]);
if (flag != null) panel.SetImage(flag);
Place(panel.transform, i, languages.Count);
panel.PlayAppear(i * 0.06f);
}
}
///
/// Le drapeau d'une langue, ou null. ⚠️ La texture vient de Resources et est
/// partagée : contrairement aux vignettes de section, elle ne se détruit pas
/// avec la tuile — Unity la garde pour toute la vie de l'app, et la détruire ferait
/// un drapeau vide à la visite suivante.
///
static Texture2D Flag(string language) =>
Resources.Load("Flags/" + Strings.Normalize(language).ToLowerInvariant());
///
/// Les tuiles sur l'arc du menu, centrées sur le regard : c'est la même géométrie
/// que , à une rangée.
///
void Place(Transform panel, int index, int count)
{
var angle = (index - (count - 1) * 0.5f) * StepDegrees;
var radians = angle * Mathf.Deg2Rad;
panel.localPosition = new Vector3(
Mathf.Sin(radians) * RadiusMeters,
HeightMeters,
Mathf.Cos(radians) * RadiusMeters);
panel.localRotation = Quaternion.Euler(0f, angle, 0f);
}
void OnSelected(IAimTarget target)
{
if (target is MenuItemPanel panel) Chosen?.Invoke(panel.Id);
}
}
}