Phase 11b B-E: ООП-гипотеза проверена end-to-end
Что в этом коммите:
Decoration реализации:
- cpp/label_decoration.hpp/.cpp — FreeType atlas + cugrid_blit_rgba_nv12.
UTF-8 декодер, atlas в VRAM (RAII через CudaBuffer), rebuild при set_text.
- cpp/border_decoration.hpp/.cpp — 4 cugrid_fill_nv12 (top/bottom/left/right).
Cell реализации:
- cpp/camera_cell.hpp/.cpp — cfc_source_get_latest + cugrid_resize_nv12.
Non-owning указатель на cfc_source_t (pool владеет).
- cpp/widget_cell.hpp/.cpp — тёмный fill placeholder.
- cpp/blank_cell.hpp/.cpp — BT.709 black fill.
Layout и Template:
- cpp/template.hpp — LayoutTemplate { name, cells[], priority }.
8×8 микро-сетка (kGridCols=kGridRows=8). to_pixels() переводит в Rect.
- cpp/layout.hpp/.cpp — vector<unique_ptr<Cell>>, apply() создаёт
CameraCell/WidgetCell/BlankCell + Decorations (Label с "{key} prio={N}").
- cpp/template_loader.hpp/.cpp — JSON → vector<LayoutTemplate> через json-c.
builtin_templates() = { tpl_1, tpl_4 } как fallback.
SourcePool:
- cpp/source_pool.hpp/.cpp — owner cfc_source_t*, motion state атомарный,
zone-filter в motion_pulse. Pool entries — non-copyable unique_ptr.
Composer:
- cpp/composer.hpp/.cpp — owner SourcePool + templates + Layout + output.
Алгоритмы: pick_best_fit (min nb_camera_cells >= need + priority tie-break),
collect_active (drawable AND motion_within_TTL), asymmetric hysteresis
(рост сразу через std::includes, сжатие — wait shrink_hysteresis_ms).
Public C++ API: set_motion_mode / set_layout / load_templates / compose_frame.
ООП-гипотеза smoke:
- examples/grid_record_cpp.cpp — минимальный smoke без NVENC. Init composer,
compose_frame N раз, dump NV12 в файл. Проверяет что C++ модель
компилируется, линкуется с C-кодом (source.c, nvenc.c остались на C через
extern "C"), и реально рисует кадр.
Производительность сохранена:
- Один output буфер VMM, передаётся как NV12Ref (read-write reference) во все
cells/decorations — НИКАКИХ memcpy на cells boundary.
- Virtual call overhead: 1 indirect call per cell per frame. Negligible.
- Heap allocations только при apply_template (раз в N секунд при relayout).
Build:
- CMakeLists.txt: CXX language, C++17.
- src/CMakeLists.txt: COMPOSER_SOURCES_CPP добавлен в lib.
- examples/CMakeLists.txt: grid_record_cpp.
Smoke test run jammy:
[cfc/loader] docker/templates.json: loaded 7 templates
[smoke] composer 1920x1080 templates=7 sources=0 motion=0
[smoke] wrote 3317760 bytes (Y=2211840 UV=1105920) to /out/blank.nv12
Build PASS, init PASS, compose PASS, dump PASS.
Что НЕ сделано:
- extern "C" ABI shim для control.c / grid_record.c (старый C-композитор
всё ещё единственный для prod stack).
- Удаление старых composer.c / overlay.c / layouts.c.
- Live deploy в прод (Step 1-3 функциональность).
- JSON ZMQ hot-reload (был в Step 3 C-version, восстановить в C++).
Refs: #195 (Phase 11b C++ refactor).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
/* BlankCell — пустая cell (Phase 11b).
|
||||
*
|
||||
* Используется для "идущего идле" слота без камеры — рисует чёрный rect
|
||||
* на месте cell. Альтернативно может быть placeholder с надписью "NO SIGNAL"
|
||||
* через LabelDecoration.
|
||||
*/
|
||||
|
||||
#ifndef CUFRAMES_COMPOSER_CPP_BLANK_CELL_HPP
|
||||
#define CUFRAMES_COMPOSER_CPP_BLANK_CELL_HPP
|
||||
|
||||
#include "cell.hpp"
|
||||
|
||||
namespace cfc {
|
||||
|
||||
class BlankCell : public Cell {
|
||||
public:
|
||||
explicit BlankCell(const Rect& geom) : Cell(geom) {}
|
||||
|
||||
protected:
|
||||
void draw_content(CUstream stream, NV12Ref& dst) override;
|
||||
};
|
||||
|
||||
} // namespace cfc
|
||||
|
||||
#endif /* CUFRAMES_COMPOSER_CPP_BLANK_CELL_HPP */
|
||||
@@ -0,0 +1,39 @@
|
||||
/* BorderDecoration — рамка вокруг cell (Phase 11b).
|
||||
*
|
||||
* 4 узких прямоугольника (top/bottom/left/right) через cfc_cugrid_fill_nv12.
|
||||
* Полезна для подсветки main cell в layout'е или recording-indicator'ов.
|
||||
*
|
||||
* Лицензия: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#ifndef CUFRAMES_COMPOSER_CPP_BORDER_DECORATION_HPP
|
||||
#define CUFRAMES_COMPOSER_CPP_BORDER_DECORATION_HPP
|
||||
|
||||
#include "decoration.hpp"
|
||||
|
||||
namespace cfc {
|
||||
|
||||
struct BorderStyle {
|
||||
int thickness = 3;
|
||||
int color_y = 210, color_u = 50, color_v = 100; /* BT.709 limited */
|
||||
int alpha = 240;
|
||||
bool visible = true;
|
||||
};
|
||||
|
||||
class BorderDecoration : public Decoration {
|
||||
public:
|
||||
explicit BorderDecoration(const BorderStyle& style) : style_(style) {}
|
||||
~BorderDecoration() override = default;
|
||||
|
||||
void set_visible(bool v) noexcept { style_.visible = v; }
|
||||
void set_style(const BorderStyle& s) noexcept { style_ = s; }
|
||||
|
||||
void draw(CUstream stream, NV12Ref& dst, const Rect& parent_rect) override;
|
||||
|
||||
private:
|
||||
BorderStyle style_;
|
||||
};
|
||||
|
||||
} // namespace cfc
|
||||
|
||||
#endif /* CUFRAMES_COMPOSER_CPP_BORDER_DECORATION_HPP */
|
||||
@@ -0,0 +1,39 @@
|
||||
/* CameraCell — рисует кадр из cuframes-источника в свой Rect (Phase 11b).
|
||||
*
|
||||
* Cell держит non-owning указатель на cfc_source_t (живёт в SourcePool
|
||||
* композитора). На каждом draw_content():
|
||||
* 1. cfc_source_get_latest — snapshot последнего кадра в VRAM
|
||||
* 2. если ACTIVE/STALE — cfc_cugrid_resize_nv12 в свою geom_
|
||||
* 3. если DEAD/CONNECTING — пропуск (cell остаётся blacked out)
|
||||
*
|
||||
* Decorations (label, border) рисуются в Cell::draw() поверх content'а.
|
||||
*
|
||||
* Лицензия: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#ifndef CUFRAMES_COMPOSER_CPP_CAMERA_CELL_HPP
|
||||
#define CUFRAMES_COMPOSER_CPP_CAMERA_CELL_HPP
|
||||
|
||||
#include "../source.h"
|
||||
#include "cell.hpp"
|
||||
|
||||
namespace cfc {
|
||||
|
||||
class CameraCell : public Cell {
|
||||
public:
|
||||
CameraCell(const Rect& geom, cfc_source_t* source)
|
||||
: Cell(geom), source_(source) {}
|
||||
|
||||
void set_source(cfc_source_t* src) noexcept { source_ = src; }
|
||||
cfc_source_t* source() const noexcept { return source_; }
|
||||
|
||||
protected:
|
||||
void draw_content(CUstream stream, NV12Ref& dst) override;
|
||||
|
||||
private:
|
||||
cfc_source_t* source_; /* non-owning — pool владеет */
|
||||
};
|
||||
|
||||
} // namespace cfc
|
||||
|
||||
#endif /* CUFRAMES_COMPOSER_CPP_CAMERA_CELL_HPP */
|
||||
@@ -0,0 +1,120 @@
|
||||
/* Composer — оркестратор Phase 11b.
|
||||
*
|
||||
* Owns:
|
||||
* - SourcePool (cuframes-источники + motion state)
|
||||
* - vector<LayoutTemplate> (loaded from JSON или builtins)
|
||||
* - Layout (текущее состояние cells)
|
||||
* - OutputSurface (CudaBuffer для NV12 output)
|
||||
*
|
||||
* Compose loop (по кадру):
|
||||
* 1. select_template_and_active() → (LayoutTemplate*, vector<PoolEntry*>)
|
||||
* по правилам: motion_mode? motion-based best-fit : idle top-1 single
|
||||
* 2. hysteresis: рост сразу, уменьшение — wait shrink_hysteresis_ms
|
||||
* 3. если sig != committed → layout_.apply(template, active, W, H)
|
||||
* 4. compose_clear() → output буфер чёрный
|
||||
* 5. layout_.render(stream, NV12Ref)
|
||||
*
|
||||
* Public API экспортируется через composer_c_api.cpp с extern "C" для
|
||||
* совместимости с control.c, grid_record.c, frigate_mqtt.c.
|
||||
*
|
||||
* Лицензия: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#ifndef CUFRAMES_COMPOSER_CPP_COMPOSER_HPP
|
||||
#define CUFRAMES_COMPOSER_CPP_COMPOSER_HPP
|
||||
|
||||
#include "cuda_raii.hpp"
|
||||
#include "layout.hpp"
|
||||
#include "source_pool.hpp"
|
||||
#include "template.hpp"
|
||||
#include "types.hpp"
|
||||
|
||||
#include <cuda_runtime.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace cfc {
|
||||
|
||||
struct ComposerConfig {
|
||||
int width = 1920;
|
||||
int height = 1080;
|
||||
int cuda_device = 0;
|
||||
int bg_y = 16, bg_u = 128, bg_v = 128;
|
||||
|
||||
/* Motion-mode параметры. */
|
||||
int motion_ttl_ms = 45000;
|
||||
int shrink_hysteresis_ms = 3000;
|
||||
|
||||
/* Templates JSON path (empty → built-in). */
|
||||
std::string templates_path;
|
||||
};
|
||||
|
||||
class Composer {
|
||||
public:
|
||||
explicit Composer(const ComposerConfig& cfg);
|
||||
~Composer();
|
||||
|
||||
Composer(const Composer&) = delete;
|
||||
Composer& operator=(const Composer&) = delete;
|
||||
|
||||
bool ok() const noexcept { return output_.ok(); }
|
||||
|
||||
SourcePool& pool() noexcept { return pool_; }
|
||||
const SourcePool& pool() const noexcept { return pool_; }
|
||||
|
||||
/* Motion mode + relayout policy. */
|
||||
void set_motion_mode(bool on, int ttl_ms = 0);
|
||||
bool motion_mode() const noexcept { return motion_mode_; }
|
||||
|
||||
/* Загрузить templates из JSON. Возвращает количество, либо <0. */
|
||||
int load_templates(const std::string& path);
|
||||
|
||||
/* Перейти на named template (только если motion_mode == false). */
|
||||
bool set_layout(const std::string& name);
|
||||
|
||||
const std::string& current_layout_name() const noexcept {
|
||||
return layout_.name();
|
||||
}
|
||||
int templates_count() const noexcept {
|
||||
return static_cast<int>(templates_.size());
|
||||
}
|
||||
const std::vector<LayoutTemplate>& templates() const noexcept {
|
||||
return templates_;
|
||||
}
|
||||
|
||||
/* Один кадр: relayout (если нужно) + clear + render.
|
||||
* Возвращает NV12Ref на output (ptr действителен до следующего compose). */
|
||||
NV12Ref compose_frame();
|
||||
|
||||
private:
|
||||
/* Selection + hysteresis. */
|
||||
const LayoutTemplate* pick_best_fit(int need) const;
|
||||
std::vector<PoolEntry*> collect_active() const;
|
||||
void maybe_relayout();
|
||||
static std::string build_signature(const std::string& tpl_name,
|
||||
const std::vector<PoolEntry*>& active);
|
||||
|
||||
ComposerConfig cfg_;
|
||||
SourcePool pool_;
|
||||
std::vector<LayoutTemplate> templates_;
|
||||
Layout layout_;
|
||||
|
||||
/* Output NV12 буфер (VMM, zero-copy для NVENC). */
|
||||
CudaBuffer output_;
|
||||
int pitch_y_ = 0;
|
||||
int pitch_uv_ = 0;
|
||||
|
||||
cudaStream_t stream_ = nullptr; /* default = 0 */
|
||||
|
||||
bool motion_mode_ = false;
|
||||
std::int64_t committed_at_ms_ = 0;
|
||||
std::int64_t pending_first_seen_ms_ = 0;
|
||||
std::string committed_signature_;
|
||||
std::string pending_signature_;
|
||||
};
|
||||
|
||||
} // namespace cfc
|
||||
|
||||
#endif /* CUFRAMES_COMPOSER_CPP_COMPOSER_HPP */
|
||||
@@ -0,0 +1,78 @@
|
||||
/* LabelDecoration — текстовая подпись поверх cell (Phase 11b).
|
||||
*
|
||||
* Рендерит UTF-8 строку через FreeType в RGBA-атлас (создаётся один раз
|
||||
* при ctor/set_text, держится в VRAM), затем на каждом draw() блитит
|
||||
* атлас в указанный угол parent_rect через cfc_cugrid_blit_rgba_nv12.
|
||||
*
|
||||
* Корнер: top-left (cell.x + pad, cell.y + pad). Pad по умолчанию 8 px.
|
||||
* Цвет, размер шрифта, alpha-множитель задаются в ctor.
|
||||
*
|
||||
* Лицензия: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#ifndef CUFRAMES_COMPOSER_CPP_LABEL_DECORATION_HPP
|
||||
#define CUFRAMES_COMPOSER_CPP_LABEL_DECORATION_HPP
|
||||
|
||||
#include "decoration.hpp"
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
#include <cuda.h>
|
||||
#include <string>
|
||||
|
||||
namespace cfc {
|
||||
|
||||
struct LabelStyle {
|
||||
std::string font_path = "/fonts/DejaVuSans-Bold.ttf";
|
||||
int pixel_size = 22;
|
||||
int r = 255, g = 220, b = 64; /* жёлто-оранжевый, читается на любом фоне */
|
||||
int alpha = 255; /* множитель прозрачности 0..255 */
|
||||
int pad = 8; /* отступ от угла cell */
|
||||
|
||||
/* visible можно переключать без перерендера атласа. */
|
||||
bool visible = true;
|
||||
};
|
||||
|
||||
class LabelDecoration : public Decoration {
|
||||
public:
|
||||
LabelDecoration(const std::string& text, const LabelStyle& style);
|
||||
~LabelDecoration() override;
|
||||
|
||||
LabelDecoration(const LabelDecoration&) = delete;
|
||||
LabelDecoration& operator=(const LabelDecoration&) = delete;
|
||||
|
||||
void set_visible(bool v) noexcept { style_.visible = v; }
|
||||
bool visible() const noexcept { return style_.visible; }
|
||||
|
||||
/* Обновить текст (re-render atlas). Передвижение/visible — без re-render. */
|
||||
void set_text(const std::string& text);
|
||||
|
||||
void draw(CUstream stream, NV12Ref& dst, const Rect& parent_rect) override;
|
||||
|
||||
private:
|
||||
/* Pass1: измерить bbox строки + ascent для baseline'а. */
|
||||
bool measure(int& w, int& h, int& ascent) const;
|
||||
/* Pass2: отрисовать строку в RGBA-буфер CPU. */
|
||||
void render_to_cpu(unsigned char* rgba, int w, int h, int ascent) const;
|
||||
/* Rebuild VRAM atlas из текущей строки. */
|
||||
bool rebuild_atlas();
|
||||
|
||||
/* FreeType state. */
|
||||
FT_Library ft_lib_ = nullptr;
|
||||
FT_Face face_ = nullptr;
|
||||
|
||||
/* Текст и стиль. */
|
||||
std::string text_;
|
||||
LabelStyle style_;
|
||||
|
||||
/* VRAM atlas. */
|
||||
CUdeviceptr atlas_ = 0;
|
||||
int atlas_w_ = 0;
|
||||
int atlas_h_ = 0;
|
||||
int atlas_pitch_ = 0;
|
||||
};
|
||||
|
||||
} // namespace cfc
|
||||
|
||||
#endif /* CUFRAMES_COMPOSER_CPP_LABEL_DECORATION_HPP */
|
||||
@@ -0,0 +1,57 @@
|
||||
/* Layout — контейнер cells и оркестратор apply_template (Phase 11b).
|
||||
*
|
||||
* Layout::apply() принимает LayoutTemplate + список активных pool-entries
|
||||
* (sorted by priority DESC) + output W×H. Создаёт нужные Cell-подклассы:
|
||||
*
|
||||
* CameraCell для каждой template-cell с role=CAMERA — берёт source
|
||||
* по индексу из active list (active[0]=order 0, active[1]=order 1, ...)
|
||||
* Если active меньше чем camera-cells — лишние cells = BlankCell.
|
||||
* WidgetCell для template-cell с role=WIDGET — placeholder.
|
||||
*
|
||||
* Decorations добавляются здесь же:
|
||||
* LabelDecoration "{key} prio={N}" в каждый CameraCell.
|
||||
* LabelDecoration с именем widget'а в каждый WidgetCell.
|
||||
* (Border, Badge — Phase 12+)
|
||||
*
|
||||
* Layout::render(stream, dst) — итеративно вызывает cell->draw().
|
||||
*
|
||||
* Лицензия: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#ifndef CUFRAMES_COMPOSER_CPP_LAYOUT_HPP
|
||||
#define CUFRAMES_COMPOSER_CPP_LAYOUT_HPP
|
||||
|
||||
#include "cell.hpp"
|
||||
#include "source_pool.hpp"
|
||||
#include "template.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace cfc {
|
||||
|
||||
class Layout {
|
||||
public:
|
||||
Layout() = default;
|
||||
|
||||
/* Применить template — пересоздаёт cells + decorations.
|
||||
* active_sorted — список pool-entries, уже отсортированный priority DESC. */
|
||||
void apply(const LayoutTemplate& tpl,
|
||||
const std::vector<PoolEntry*>& active_sorted,
|
||||
int frame_w, int frame_h);
|
||||
|
||||
/* Прорисовать все cells в dst буфер. */
|
||||
void render(CUstream stream, NV12Ref& dst);
|
||||
|
||||
const std::string& name() const noexcept { return current_name_; }
|
||||
int cell_count() const noexcept { return static_cast<int>(cells_.size()); }
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<Cell>> cells_;
|
||||
std::string current_name_;
|
||||
};
|
||||
|
||||
} // namespace cfc
|
||||
|
||||
#endif /* CUFRAMES_COMPOSER_CPP_LAYOUT_HPP */
|
||||
@@ -0,0 +1,95 @@
|
||||
/* SourcePool — пул cuframes-источников композитора (Phase 11b).
|
||||
*
|
||||
* Каждая запись: cuframes_key + frigate_camera + priority + cfc_source_t* +
|
||||
* motion state (last_motion_ms, zone-filter). Pool создаётся при старте
|
||||
* композитора (через add() вызовы) и живёт всю сессию.
|
||||
*
|
||||
* Cells (CameraCell) держат non-owning указатели на cfc_source_t — pool
|
||||
* владеет.
|
||||
*
|
||||
* Лицензия: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#ifndef CUFRAMES_COMPOSER_CPP_SOURCE_POOL_HPP
|
||||
#define CUFRAMES_COMPOSER_CPP_SOURCE_POOL_HPP
|
||||
|
||||
#include "../source.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace cfc {
|
||||
|
||||
struct PoolEntry {
|
||||
std::string cuframes_key;
|
||||
std::string frigate_camera;
|
||||
int priority = 0;
|
||||
cfc_source_t* source = nullptr;
|
||||
std::atomic<std::int64_t> last_motion_ms{0};
|
||||
std::vector<std::string> required_zones;
|
||||
|
||||
/* Получить snapshot для drawable-checks без локов. */
|
||||
cfc_source_state_t state() const {
|
||||
if (!source) return CFC_SOURCE_DISCONNECTED;
|
||||
cfc_source_snapshot_t s{};
|
||||
cfc_source_get_latest(source, &s);
|
||||
return s.state;
|
||||
}
|
||||
|
||||
bool drawable() const {
|
||||
cfc_source_state_t st = state();
|
||||
return st == CFC_SOURCE_ACTIVE || st == CFC_SOURCE_STALE;
|
||||
}
|
||||
};
|
||||
|
||||
class SourcePool {
|
||||
public:
|
||||
SourcePool() = default;
|
||||
~SourcePool();
|
||||
|
||||
SourcePool(const SourcePool&) = delete;
|
||||
SourcePool& operator=(const SourcePool&) = delete;
|
||||
|
||||
/* Параметры подписки cuframes (default per cfc_source_config_t). */
|
||||
struct SubscribeOpts {
|
||||
int cuda_device = 0;
|
||||
std::string consumer_prefix = "composer";
|
||||
int reconnect_min_ms = 1000;
|
||||
int reconnect_max_ms = 30000;
|
||||
int stale_threshold_ms = 500;
|
||||
int dead_threshold_ms = 5000;
|
||||
};
|
||||
|
||||
/* Добавить источник в pool. Возвращает индекс или -1. */
|
||||
int add(const std::string& cuframes_key,
|
||||
const std::string& frigate_camera,
|
||||
int priority,
|
||||
const std::vector<std::string>& zones,
|
||||
const SubscribeOpts& opts);
|
||||
|
||||
int size() const { return static_cast<int>(entries_.size()); }
|
||||
PoolEntry* by_index(int i) { return i >= 0 && i < size() ? entries_[i].get() : nullptr; }
|
||||
PoolEntry* by_key(const std::string& key);
|
||||
|
||||
/* Уведомить о motion (вызывается из Frigate MQTT subscriber'а через
|
||||
* C-shim). Если zone-filter задан — проверяет пересечение. */
|
||||
void motion_pulse(const std::string& frigate_camera,
|
||||
const std::vector<std::string>& current_zones);
|
||||
|
||||
/* Итерация (для best-fit selection и health). */
|
||||
template <typename F>
|
||||
void for_each(F&& fn) {
|
||||
for (auto& e : entries_) fn(*e);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<PoolEntry>> entries_;
|
||||
std::mutex mu_;
|
||||
};
|
||||
|
||||
} // namespace cfc
|
||||
|
||||
#endif /* CUFRAMES_COMPOSER_CPP_SOURCE_POOL_HPP */
|
||||
@@ -0,0 +1,69 @@
|
||||
/* Layout template — описание сетки в микроячейках (Phase 11b).
|
||||
*
|
||||
* Template — declarative описание layout'а: имя, набор CellTemplate
|
||||
* (col/row/cs/rs/role/order/widget). Layout::apply_template() из template'а
|
||||
* + SourcePool создаёт конкретные Cell-объекты (CameraCell/WidgetCell).
|
||||
*
|
||||
* Грид: 8×8 микроячейки на output W×H. Для 1920×1080 микроячейка = 240×135 (16:9).
|
||||
*
|
||||
* Загружается из JSON через template_loader.
|
||||
*
|
||||
* Лицензия: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#ifndef CUFRAMES_COMPOSER_CPP_TEMPLATE_HPP
|
||||
#define CUFRAMES_COMPOSER_CPP_TEMPLATE_HPP
|
||||
|
||||
#include "types.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace cfc {
|
||||
|
||||
constexpr int kGridCols = 8;
|
||||
constexpr int kGridRows = 8;
|
||||
|
||||
enum class CellRole {
|
||||
Camera = 0,
|
||||
Widget = 1,
|
||||
};
|
||||
|
||||
struct CellTemplate {
|
||||
int col = 0, row = 0;
|
||||
int cs = 1, rs = 1;
|
||||
CellRole role = CellRole::Camera;
|
||||
int order = 0;
|
||||
std::string widget; /* имя widget'а для role=Widget */
|
||||
};
|
||||
|
||||
struct LayoutTemplate {
|
||||
std::string name;
|
||||
int priority = 0;
|
||||
std::vector<CellTemplate> cells;
|
||||
|
||||
int nb_camera_cells() const {
|
||||
int n = 0;
|
||||
for (auto& c : cells) if (c.role == CellRole::Camera) ++n;
|
||||
return n;
|
||||
}
|
||||
};
|
||||
|
||||
/* Перевести {col,row,cs,rs} в pixel-rect для output W×H. */
|
||||
inline Rect to_pixels(const CellTemplate& c, int W, int H)
|
||||
{
|
||||
Rect r;
|
||||
r.x = (c.col * W) / kGridCols;
|
||||
r.y = (c.row * H) / kGridRows;
|
||||
r.w = (c.cs * W) / kGridCols;
|
||||
r.h = (c.rs * H) / kGridRows;
|
||||
/* NV12 4:2:0 — чётные. */
|
||||
r.x &= ~1; r.y &= ~1; r.w &= ~1; r.h &= ~1;
|
||||
if (r.x + r.w > W) r.w = W - r.x;
|
||||
if (r.y + r.h > H) r.h = H - r.y;
|
||||
return r;
|
||||
}
|
||||
|
||||
} // namespace cfc
|
||||
|
||||
#endif /* CUFRAMES_COMPOSER_CPP_TEMPLATE_HPP */
|
||||
@@ -0,0 +1,27 @@
|
||||
/* Template loader — JSON → vector<LayoutTemplate> (Phase 11b).
|
||||
*
|
||||
* Schema см. docker/templates.json. При неудаче возвращает empty vector
|
||||
* (caller использует built-in fallback).
|
||||
*/
|
||||
|
||||
#ifndef CUFRAMES_COMPOSER_CPP_TEMPLATE_LOADER_HPP
|
||||
#define CUFRAMES_COMPOSER_CPP_TEMPLATE_LOADER_HPP
|
||||
|
||||
#include "template.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace cfc {
|
||||
|
||||
/* Загрузить из файла. Возвращает количество загруженных templates, либо
|
||||
* отрицательное число при ошибке (-1=parse, -2=schema, -3=open). */
|
||||
int load_templates_from_file(const std::string& path,
|
||||
std::vector<LayoutTemplate>& out);
|
||||
|
||||
/* Встроенный набор fallback templates (Phase 11b base — single, quad). */
|
||||
std::vector<LayoutTemplate> builtin_templates();
|
||||
|
||||
} // namespace cfc
|
||||
|
||||
#endif /* CUFRAMES_COMPOSER_CPP_TEMPLATE_LOADER_HPP */
|
||||
@@ -0,0 +1,34 @@
|
||||
/* WidgetCell — заглушка для widget'а (Phase 11b MVP).
|
||||
*
|
||||
* Фаза 11b: рисует cell тёмно-серым (Y=40) + label-decoration с именем
|
||||
* widget'а в центре. Реальные виджеты (graph, ha_chat) — Phase 12+.
|
||||
*
|
||||
* Лицензия: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#ifndef CUFRAMES_COMPOSER_CPP_WIDGET_CELL_HPP
|
||||
#define CUFRAMES_COMPOSER_CPP_WIDGET_CELL_HPP
|
||||
|
||||
#include "cell.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace cfc {
|
||||
|
||||
class WidgetCell : public Cell {
|
||||
public:
|
||||
WidgetCell(const Rect& geom, const std::string& widget_name)
|
||||
: Cell(geom), widget_name_(widget_name) {}
|
||||
|
||||
const std::string& widget_name() const noexcept { return widget_name_; }
|
||||
|
||||
protected:
|
||||
void draw_content(CUstream stream, NV12Ref& dst) override;
|
||||
|
||||
private:
|
||||
std::string widget_name_;
|
||||
};
|
||||
|
||||
} // namespace cfc
|
||||
|
||||
#endif /* CUFRAMES_COMPOSER_CPP_WIDGET_CELL_HPP */
|
||||
Reference in New Issue
Block a user