using System.Collections.Generic; namespace MyInfoMate.Vr.Menu { /// /// Placement « bento » dense sur une grille de colonnes — le socle du menu flottant. /// /// Port C# de myinfomate_layout (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 grid-auto-flow: dense. /// /// Porté plutôt que calculé par le serveur : 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. /// 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 Placements; public int RowCount; } public static Result Compute(IList items, int columns) { var cols = columns < 1 ? 1 : columns; var occupied = new List(); 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(); 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; } } }