Les tuiles suivent maintenant les mêmes colSpan × rowSpan que les autres canaux, sur six colonnes — environ 83° d'ouverture, dans le confort du regard — avec un écart serré assumé : espacées, les tuiles se lisent comme des panneaux sans rapport plutôt que comme une planche. Le pas angulaire se déduit de la largeur de cellule et du rayon de l'arc, plutôt que d'être posé en dur. S'y ajoute le sélecteur de langue avec ses drapeaux, et les libellés de l'interface sortis du code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
124 lines
3.8 KiB
C#
124 lines
3.8 KiB
C#
using System.Collections.Generic;
|
|
|
|
namespace MyInfoMate.Vr.Menu
|
|
{
|
|
/// <summary>
|
|
/// Placement « bento » dense sur une grille de colonnes — le socle du menu flottant.
|
|
///
|
|
/// <b>Port C# de <c>myinfomate_layout</c></b> (Dart), qui sert au manager et à
|
|
/// mymuseum-visitapp ; le web refait le même placement en CSS Grid. Trois
|
|
/// implémentations, une seule règle : chaque item va à la première cellule libre,
|
|
/// parcours ligne par ligne, qui l'accueille — soit <c>grid-auto-flow: dense</c>.
|
|
///
|
|
/// <b>Porté plutôt que calculé par le serveur</b> : l'export reste une description du
|
|
/// contenu, pas une mise en page. Un placement calculé à distance ferait qu'un vieux
|
|
/// contenu en cache porterait une vieille mise en page.
|
|
/// </summary>
|
|
public static class BentoLayout
|
|
{
|
|
public struct Item
|
|
{
|
|
public string Id;
|
|
public int ColSpan;
|
|
public int RowSpan;
|
|
|
|
public Item(string id, int colSpan = 1, int rowSpan = 1)
|
|
{
|
|
Id = id;
|
|
ColSpan = colSpan;
|
|
RowSpan = rowSpan;
|
|
}
|
|
}
|
|
|
|
public struct Placement
|
|
{
|
|
public string Id;
|
|
public int Col;
|
|
public int Row;
|
|
public int ColSpan;
|
|
public int RowSpan;
|
|
}
|
|
|
|
public struct Result
|
|
{
|
|
public List<Placement> Placements;
|
|
public int RowCount;
|
|
}
|
|
|
|
public static Result Compute(IList<Item> items, int columns)
|
|
{
|
|
var cols = columns < 1 ? 1 : columns;
|
|
var occupied = new List<bool[]>();
|
|
|
|
void EnsureRow(int r)
|
|
{
|
|
while (occupied.Count <= r) occupied.Add(new bool[cols]);
|
|
}
|
|
|
|
bool Fits(int r, int c, int cs, int rs)
|
|
{
|
|
if (c + cs > cols) return false;
|
|
for (var rr = r; rr < r + rs; rr++)
|
|
{
|
|
EnsureRow(rr);
|
|
for (var cc = c; cc < c + cs; cc++)
|
|
if (occupied[rr][cc]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Fill(int r, int c, int cs, int rs)
|
|
{
|
|
for (var rr = r; rr < r + rs; rr++)
|
|
{
|
|
EnsureRow(rr);
|
|
for (var cc = c; cc < c + cs; cc++) occupied[rr][cc] = true;
|
|
}
|
|
}
|
|
|
|
var placements = new List<Placement>();
|
|
|
|
foreach (var item in items)
|
|
{
|
|
var cs = item.ColSpan < 1 ? 1 : (item.ColSpan > cols ? cols : item.ColSpan);
|
|
var rs = item.RowSpan < 1 ? 1 : item.RowSpan;
|
|
|
|
var placed = false;
|
|
var r = 0;
|
|
while (!placed)
|
|
{
|
|
for (var c = 0; c + cs <= cols; c++)
|
|
{
|
|
if (!Fits(r, c, cs, rs)) continue;
|
|
|
|
Fill(r, c, cs, rs);
|
|
placements.Add(new Placement
|
|
{
|
|
Id = item.Id,
|
|
Col = c,
|
|
Row = r,
|
|
ColSpan = cs,
|
|
RowSpan = rs,
|
|
});
|
|
placed = true;
|
|
break;
|
|
}
|
|
if (!placed) r++;
|
|
}
|
|
}
|
|
|
|
var rowCount = occupied.Count;
|
|
while (rowCount > 0 && IsEmpty(occupied[rowCount - 1])) rowCount--;
|
|
|
|
return new Result { Placements = placements, RowCount = rowCount };
|
|
}
|
|
|
|
static bool IsEmpty(bool[] row)
|
|
{
|
|
foreach (var cell in row)
|
|
if (cell) return false;
|
|
return true;
|
|
}
|
|
}
|
|
}
|