diff --git a/docker/templates.json b/docker/templates.json index 26bd0b8..32eeb57 100644 --- a/docker/templates.json +++ b/docker/templates.json @@ -2,7 +2,7 @@ "version": 1, "grid_cols": 8, "grid_rows": 8, - "_doc": "Layout-templates для cfc-grid auto-layout. Координаты в микроячейках 8×8 (output 1920×1080 → каждая микроячейка 240×135 px, 16:9). Квадраты N×N микроячеек тоже 16:9. role=camera — заполняется из активных камер по priority. role=widget — placeholder.", + "_doc": "Phase 11b — только пропорциональные 16:9 layouts (cs == rs микроячеек). Полностью заполняют output 1920×1080 без widget-областей. При недостатке active-камер по motion свободные camera-cells заполняются остальными drawable-камерами из pool (по priority). См. cfc::Composer::maybe_relayout().", "templates": [ { @@ -12,20 +12,9 @@ {"col": 0, "row": 0, "cs": 8, "rs": 8, "role": "camera", "order": 0} ] }, - { - "name": "tpl_3", - "_desc": "Главная 1440×810 слева + 2 превью 480×270 справа стопкой, остаток — виджеты.", - "cells": [ - {"col": 0, "row": 0, "cs": 6, "rs": 6, "role": "camera", "order": 0}, - {"col": 6, "row": 0, "cs": 2, "rs": 2, "role": "camera", "order": 1}, - {"col": 6, "row": 2, "cs": 2, "rs": 2, "role": "camera", "order": 2}, - {"col": 6, "row": 4, "cs": 2, "rs": 4, "role": "widget", "widget": "temp_chart"}, - {"col": 0, "row": 6, "cs": 6, "rs": 2, "role": "widget", "widget": "ha_chat"} - ] - }, { "name": "tpl_4", - "_desc": "Quad 2×2: 4 камеры 960×540. order=0 — top-left (главная).", + "_desc": "Quad 2×2 — 4 камеры 960×540 (16:9). order=0 — top-left (главная).", "cells": [ {"col": 0, "row": 0, "cs": 4, "rs": 4, "role": "camera", "order": 0}, {"col": 4, "row": 0, "cs": 4, "rs": 4, "role": "camera", "order": 1}, @@ -34,56 +23,40 @@ ] }, { - "name": "tpl_5", - "_desc": "1 главная + 4 превью справа стопкой, нижняя полоса — виджет.", + "name": "tpl_9", + "_desc": "3×3 grid — 9 камер... только cells 2×2 микроячейки по 6×6 области. ВНИМАНИЕ: 8×8 не делится на 3 без остатка; используем 6×6 камер + bottom/right остаток как background. Если потом будут asymmetric с widget'ами — выделим Phase 12.", "cells": [ - {"col": 0, "row": 0, "cs": 6, "rs": 6, "role": "camera", "order": 0}, - {"col": 6, "row": 0, "cs": 2, "rs": 2, "role": "camera", "order": 1}, - {"col": 6, "row": 2, "cs": 2, "rs": 2, "role": "camera", "order": 2}, - {"col": 6, "row": 4, "cs": 2, "rs": 2, "role": "camera", "order": 3}, - {"col": 6, "row": 6, "cs": 2, "rs": 2, "role": "camera", "order": 4}, - {"col": 0, "row": 6, "cs": 6, "rs": 2, "role": "widget", "widget": "ha_chat"} + {"col": 0, "row": 0, "cs": 2, "rs": 2, "role": "camera", "order": 0}, + {"col": 2, "row": 0, "cs": 2, "rs": 2, "role": "camera", "order": 1}, + {"col": 4, "row": 0, "cs": 2, "rs": 2, "role": "camera", "order": 2}, + {"col": 0, "row": 2, "cs": 2, "rs": 2, "role": "camera", "order": 3}, + {"col": 2, "row": 2, "cs": 2, "rs": 2, "role": "camera", "order": 4}, + {"col": 4, "row": 2, "cs": 2, "rs": 2, "role": "camera", "order": 5}, + {"col": 0, "row": 4, "cs": 2, "rs": 2, "role": "camera", "order": 6}, + {"col": 2, "row": 4, "cs": 2, "rs": 2, "role": "camera", "order": 7}, + {"col": 4, "row": 4, "cs": 2, "rs": 2, "role": "camera", "order": 8} ] }, { - "name": "tpl_6", - "_desc": "1 главная + 3 правые + 2 нижние, остаток — виджет.", + "name": "tpl_16", + "_desc": "4×4 grid — 16 камер по 480×270 (16:9). Полностью покрывает 8×8 без остатка.", "cells": [ - {"col": 0, "row": 0, "cs": 6, "rs": 6, "role": "camera", "order": 0}, - {"col": 6, "row": 0, "cs": 2, "rs": 2, "role": "camera", "order": 1}, - {"col": 6, "row": 2, "cs": 2, "rs": 2, "role": "camera", "order": 2}, - {"col": 6, "row": 4, "cs": 2, "rs": 2, "role": "camera", "order": 3}, - {"col": 0, "row": 6, "cs": 2, "rs": 2, "role": "camera", "order": 4}, - {"col": 2, "row": 6, "cs": 2, "rs": 2, "role": "camera", "order": 5}, - {"col": 4, "row": 6, "cs": 4, "rs": 2, "role": "widget", "widget": "info"} - ] - }, - { - "name": "tpl_7", - "_desc": "1 главная + 3 правые + 3 нижние, угол — виджет.", - "cells": [ - {"col": 0, "row": 0, "cs": 6, "rs": 6, "role": "camera", "order": 0}, - {"col": 6, "row": 0, "cs": 2, "rs": 2, "role": "camera", "order": 1}, - {"col": 6, "row": 2, "cs": 2, "rs": 2, "role": "camera", "order": 2}, - {"col": 6, "row": 4, "cs": 2, "rs": 2, "role": "camera", "order": 3}, - {"col": 0, "row": 6, "cs": 2, "rs": 2, "role": "camera", "order": 4}, - {"col": 2, "row": 6, "cs": 2, "rs": 2, "role": "camera", "order": 5}, - {"col": 4, "row": 6, "cs": 2, "rs": 2, "role": "camera", "order": 6}, - {"col": 6, "row": 6, "cs": 2, "rs": 2, "role": "widget", "widget": "ha_chat"} - ] - }, - { - "name": "tpl_8", - "_desc": "1+3+4 — главная + 3 правые + полная нижняя строка.", - "cells": [ - {"col": 0, "row": 0, "cs": 6, "rs": 6, "role": "camera", "order": 0}, - {"col": 6, "row": 0, "cs": 2, "rs": 2, "role": "camera", "order": 1}, - {"col": 6, "row": 2, "cs": 2, "rs": 2, "role": "camera", "order": 2}, - {"col": 6, "row": 4, "cs": 2, "rs": 2, "role": "camera", "order": 3}, - {"col": 0, "row": 6, "cs": 2, "rs": 2, "role": "camera", "order": 4}, - {"col": 2, "row": 6, "cs": 2, "rs": 2, "role": "camera", "order": 5}, - {"col": 4, "row": 6, "cs": 2, "rs": 2, "role": "camera", "order": 6}, - {"col": 6, "row": 6, "cs": 2, "rs": 2, "role": "camera", "order": 7} + {"col": 0, "row": 0, "cs": 2, "rs": 2, "role": "camera", "order": 0}, + {"col": 2, "row": 0, "cs": 2, "rs": 2, "role": "camera", "order": 1}, + {"col": 4, "row": 0, "cs": 2, "rs": 2, "role": "camera", "order": 2}, + {"col": 6, "row": 0, "cs": 2, "rs": 2, "role": "camera", "order": 3}, + {"col": 0, "row": 2, "cs": 2, "rs": 2, "role": "camera", "order": 4}, + {"col": 2, "row": 2, "cs": 2, "rs": 2, "role": "camera", "order": 5}, + {"col": 4, "row": 2, "cs": 2, "rs": 2, "role": "camera", "order": 6}, + {"col": 6, "row": 2, "cs": 2, "rs": 2, "role": "camera", "order": 7}, + {"col": 0, "row": 4, "cs": 2, "rs": 2, "role": "camera", "order": 8}, + {"col": 2, "row": 4, "cs": 2, "rs": 2, "role": "camera", "order": 9}, + {"col": 4, "row": 4, "cs": 2, "rs": 2, "role": "camera", "order": 10}, + {"col": 6, "row": 4, "cs": 2, "rs": 2, "role": "camera", "order": 11}, + {"col": 0, "row": 6, "cs": 2, "rs": 2, "role": "camera", "order": 12}, + {"col": 2, "row": 6, "cs": 2, "rs": 2, "role": "camera", "order": 13}, + {"col": 4, "row": 6, "cs": 2, "rs": 2, "role": "camera", "order": 14}, + {"col": 6, "row": 6, "cs": 2, "rs": 2, "role": "camera", "order": 15} ] } ] diff --git a/src/cpp/composer.cpp b/src/cpp/composer.cpp index 330e4fe..8bf2266 100644 --- a/src/cpp/composer.cpp +++ b/src/cpp/composer.cpp @@ -215,6 +215,26 @@ void Composer::maybe_relayout() int cap = tpl->nb_camera_cells(); if (static_cast(active.size()) > cap) active.resize(cap); + /* Если template имеет больше camera-cells чем активных по motion — + * заполнить оставшиеся drawable камерами из pool (по priority), + * которые ещё не вошли в active. Это убирает "чёрные ячейки" + * в asymmetric layouts (tpl_3/5/6/7 + tpl_4 при active<4). */ + if (static_cast(active.size()) < cap) { + std::vector already(active.begin(), active.end()); + std::vector extras; + const_cast(pool_).for_each([&](PoolEntry& e) { + if (!e.drawable()) return; + for (auto* a : already) if (a == &e) return; + extras.push_back(&e); + }); + std::sort(extras.begin(), extras.end(), + [](PoolEntry* a, PoolEntry* b) { return a->priority > b->priority; }); + for (auto* e : extras) { + if (static_cast(active.size()) >= cap) break; + active.push_back(e); + } + } + std::string sig = build_signature(tpl->name, active); if (sig == committed_signature_) {