229 lines
8.0 KiB
C++
229 lines
8.0 KiB
C++
#include "RoomCardTileWidget.hpp"
|
|
#include "../Fonts.hpp"
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
|
|
RoomCardTileWidget::RoomCardTileWidget(const WidgetConfig& config)
|
|
: RoomCardWidgetBase(config)
|
|
{
|
|
for (size_t i = 0; i < MAX_TEXTLINES; ++i) {
|
|
textLineLabels_[i] = nullptr;
|
|
}
|
|
}
|
|
|
|
void RoomCardTileWidget::clearLvglObject() {
|
|
RoomCardWidgetBase::clearLvglObject();
|
|
decorIcon_ = nullptr;
|
|
for (size_t i = 0; i < MAX_TEXTLINES; ++i) {
|
|
textLineLabels_[i] = nullptr;
|
|
}
|
|
}
|
|
|
|
lv_obj_t* RoomCardTileWidget::create(lv_obj_t* parent) {
|
|
parseTextBase(); // Parses roomName_ from first line of text
|
|
|
|
screenParent_ = parent;
|
|
|
|
// Create main container
|
|
obj_ = lv_obj_create(parent);
|
|
lv_obj_remove_style_all(obj_);
|
|
lv_obj_set_pos(obj_, config_.x, config_.y);
|
|
lv_obj_set_size(obj_, config_.width, config_.height);
|
|
lv_obj_clear_flag(obj_, LV_OBJ_FLAG_SCROLLABLE);
|
|
lv_obj_add_flag(obj_, LV_OBJ_FLAG_CLICKABLE);
|
|
|
|
// Add click handler for navigation
|
|
lv_obj_add_event_cb(obj_, cardClickCallback, LV_EVENT_CLICKED, this);
|
|
|
|
createTileLayout();
|
|
createTextLines();
|
|
createSubButtonsCommon();
|
|
|
|
return obj_;
|
|
}
|
|
|
|
void RoomCardTileWidget::createTileLayout() {
|
|
// Tile style: rectangular card with rounded corners
|
|
int16_t radius = config_.borderRadius;
|
|
if (radius <= 0 || radius > 50) {
|
|
radius = 16;
|
|
}
|
|
lv_obj_set_style_radius(obj_, radius, 0);
|
|
lv_obj_set_style_bg_opa(obj_, config_.bgOpacity, 0);
|
|
lv_obj_set_style_bg_color(obj_, lv_color_hex(config_.bgColor.toLvColor()), 0);
|
|
lv_obj_set_style_clip_corner(obj_, true, 0);
|
|
|
|
int16_t padding = 16;
|
|
|
|
// Room name (top-left)
|
|
roomLabel_ = lv_label_create(obj_);
|
|
lv_obj_clear_flag(roomLabel_, LV_OBJ_FLAG_CLICKABLE);
|
|
lv_label_set_text(roomLabel_, roomName_);
|
|
lv_obj_set_pos(roomLabel_, padding, padding);
|
|
|
|
// Large decorative icon (bottom-left, partially visible)
|
|
if (config_.iconCodepoint > 0 && Fonts::hasIconFont()) {
|
|
decorIcon_ = lv_label_create(obj_);
|
|
lv_obj_clear_flag(decorIcon_, LV_OBJ_FLAG_CLICKABLE);
|
|
char iconText[5];
|
|
encodeUtf8(config_.iconCodepoint, iconText);
|
|
lv_label_set_text(decorIcon_, iconText);
|
|
|
|
int16_t iconX = (config_.iconPositionX != 0) ? config_.iconPositionX : -20;
|
|
int16_t iconY = (config_.iconPositionY != 0) ? config_.iconPositionY : config_.height - 120;
|
|
lv_obj_set_pos(decorIcon_, iconX, iconY);
|
|
}
|
|
}
|
|
|
|
void RoomCardTileWidget::createTextLines() {
|
|
if (config_.textLineCount == 0) return;
|
|
|
|
int16_t padding = 16;
|
|
int16_t currentY = 48; // Below room name
|
|
|
|
for (uint8_t i = 0; i < config_.textLineCount && i < MAX_TEXTLINES; ++i) {
|
|
const TextLineConfig& tl = config_.textLines[i];
|
|
if (!tl.enabled) continue;
|
|
|
|
// Get font for this line
|
|
uint8_t fontIdx = tl.fontSize;
|
|
if (fontIdx > 5) fontIdx = 1; // Default to 18px
|
|
const lv_font_t* lineFont = Fonts::bySizeIndex(fontIdx);
|
|
const lv_font_t* iconFont = Fonts::iconFont(fontIdx);
|
|
|
|
// Create a row container
|
|
lv_obj_t* row = lv_obj_create(obj_);
|
|
lv_obj_remove_style_all(row);
|
|
lv_obj_set_size(row, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
|
lv_obj_set_pos(row, padding, currentY);
|
|
lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW);
|
|
lv_obj_set_flex_align(row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
|
lv_obj_set_style_pad_gap(row, 6, 0);
|
|
lv_obj_clear_flag(row, LV_OBJ_FLAG_SCROLLABLE);
|
|
lv_obj_clear_flag(row, LV_OBJ_FLAG_CLICKABLE);
|
|
|
|
// Create icon if set
|
|
if (tl.iconCodepoint > 0 && Fonts::hasIconFont()) {
|
|
lv_obj_t* icon = lv_label_create(row);
|
|
lv_obj_clear_flag(icon, LV_OBJ_FLAG_CLICKABLE);
|
|
char iconText[5];
|
|
encodeUtf8(tl.iconCodepoint, iconText);
|
|
lv_label_set_text(icon, iconText);
|
|
if (iconFont) {
|
|
lv_obj_set_style_text_font(icon, iconFont, 0);
|
|
}
|
|
lv_obj_set_style_text_color(icon, lv_color_hex(config_.textColor.toLvColor()), 0);
|
|
}
|
|
|
|
// Create value label
|
|
lv_obj_t* label = lv_label_create(row);
|
|
lv_obj_clear_flag(label, LV_OBJ_FLAG_CLICKABLE);
|
|
|
|
// Set initial text based on source
|
|
if (tl.textSrc == TextSource::STATIC) {
|
|
lv_label_set_text(label, tl.text);
|
|
} else {
|
|
lv_label_set_text(label, "--");
|
|
}
|
|
|
|
if (lineFont) {
|
|
lv_obj_set_style_text_font(label, lineFont, 0);
|
|
}
|
|
lv_obj_set_style_text_color(label, lv_color_hex(config_.textColor.toLvColor()), 0);
|
|
|
|
textLineLabels_[i] = label;
|
|
|
|
// Move Y position for next line
|
|
currentY += lv_font_get_line_height(lineFont) + 8;
|
|
}
|
|
}
|
|
|
|
void RoomCardTileWidget::calculateSubButtonPosition(uint8_t index, const SubButtonConfig& cfg, int16_t& x, int16_t& y) {
|
|
(void)cfg; // Not used for Tile layout
|
|
|
|
// Buttons are stacked vertically on the right side
|
|
int16_t subBtnSize = config_.subButtonSize > 0 ? config_.subButtonSize : 40;
|
|
int16_t gap = 10;
|
|
int16_t padding = 12;
|
|
|
|
x = config_.width - subBtnSize - padding;
|
|
y = padding + index * (subBtnSize + gap);
|
|
}
|
|
|
|
void RoomCardTileWidget::updateTextLineValue(uint8_t index, float value) {
|
|
if (index >= MAX_TEXTLINES || !textLineLabels_[index]) return;
|
|
|
|
const TextLineConfig& tl = config_.textLines[index];
|
|
if (tl.textSrc == TextSource::STATIC) return;
|
|
|
|
char buf[32];
|
|
if (tl.text[0] != '\0') {
|
|
snprintf(buf, sizeof(buf), tl.text, value);
|
|
} else {
|
|
// Default formats based on source type
|
|
switch (tl.textSrc) {
|
|
case TextSource::KNX_DPT_TEMP:
|
|
snprintf(buf, sizeof(buf), "%.1f C", value);
|
|
break;
|
|
case TextSource::KNX_DPT_PERCENT:
|
|
snprintf(buf, sizeof(buf), "%.0f %%", value);
|
|
break;
|
|
case TextSource::KNX_DPT_POWER:
|
|
snprintf(buf, sizeof(buf), "%.1f W", value);
|
|
break;
|
|
case TextSource::KNX_DPT_ENERGY:
|
|
snprintf(buf, sizeof(buf), "%.0f kWh", value);
|
|
break;
|
|
default:
|
|
snprintf(buf, sizeof(buf), "%.1f", value);
|
|
break;
|
|
}
|
|
}
|
|
lv_label_set_text(textLineLabels_[index], buf);
|
|
}
|
|
|
|
void RoomCardTileWidget::onTextLineValue(uint8_t index, float value) {
|
|
updateTextLineValue(index, value);
|
|
}
|
|
|
|
void RoomCardTileWidget::applyStyle() {
|
|
if (!obj_) return;
|
|
|
|
lv_color_t textColor = lv_color_hex(config_.textColor.toLvColor());
|
|
|
|
if (config_.borderWidth > 0 && config_.borderOpacity > 0) {
|
|
lv_obj_set_style_border_width(obj_, config_.borderWidth, 0);
|
|
lv_obj_set_style_border_color(obj_, lv_color_hex(config_.borderColor.toLvColor()), 0);
|
|
lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0);
|
|
} else if (config_.shadow.enabled) {
|
|
// Backward-compatible accent border behavior
|
|
lv_obj_set_style_border_width(obj_, 3, 0);
|
|
lv_obj_set_style_border_color(obj_, lv_color_hex(config_.shadow.color.toLvColor()), 0);
|
|
lv_obj_set_style_border_opa(obj_, 255, 0);
|
|
} else {
|
|
lv_obj_set_style_border_width(obj_, 0, 0);
|
|
}
|
|
|
|
// Room name - large font
|
|
if (roomLabel_) {
|
|
const lv_font_t* nameFont = Fonts::bySizeIndex(config_.fontSize);
|
|
lv_obj_set_style_text_font(roomLabel_, nameFont, 0);
|
|
lv_obj_set_style_text_color(roomLabel_, textColor, 0);
|
|
}
|
|
|
|
// Large decorative icon
|
|
if (decorIcon_) {
|
|
uint8_t decorIconIdx = config_.iconSize;
|
|
if (decorIconIdx < 6) decorIconIdx = 8; // Default to 96px for decorative
|
|
const lv_font_t* bigIconFont = Fonts::iconFont(decorIconIdx);
|
|
if (bigIconFont) {
|
|
lv_obj_set_style_text_font(decorIcon_, bigIconFont, 0);
|
|
lv_obj_set_style_text_color(decorIcon_, textColor, 0);
|
|
lv_obj_set_style_text_opa(decorIcon_, 50, 0); // Very transparent
|
|
}
|
|
}
|
|
|
|
// Style sub-buttons
|
|
applySubButtonStyle();
|
|
}
|