Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 111 additions & 76 deletions src/front-lvgl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <src/misc/lv_area.h>
#include <src/misc/lv_color.h>
#include <src/misc/lv_style.h>
#include <src/widgets/tabview/lv_tabview.h>
#include <stdexcept>
#include <string>

Expand All @@ -41,6 +42,8 @@ namespace lvgl
{
lv_font_t* b612font;
lv_style_t b612style;
lv_font_t* b612fontbig;
lv_style_t b612stylebig;
lv_font_t* mdifont;
lv_style_t mdistyle;
map<iconkey, string> iconmap; // will need a lock eventually
Expand All @@ -49,7 +52,6 @@ namespace lvgl
}

std::mutex g_lvgl_updatelock;
lv_obj_t* cont_row;
namespace
{
template <typename UIType>
Expand All @@ -59,35 +61,18 @@ std::unique_ptr<UIEntity> makeUIElement(std::shared_ptr<HAEntity> _entity, lv_ob
}
}

void btnLeftPress(lv_event_t* _e)
{
lv_event_code_t code = lv_event_get_code(_e);
if (code == LV_EVENT_CLICKED) {
lv_coord_t x = lv_obj_get_scroll_x(cont_row);
// this is 807 because for whatever reason the snapping requires it to be 807....
lv_obj_scroll_to_x(cont_row, x - 807, LV_ANIM_OFF);
}
};

void btnRightPress(lv_event_t* _e)
{
lv_event_code_t code = lv_event_get_code(_e);
if (code == LV_EVENT_CLICKED) {
lv_coord_t x = lv_obj_get_scroll_x(cont_row);
lv_obj_scroll_to_x(cont_row, x + 807, LV_ANIM_OFF);
}
};

void lvLogCallback(lv_log_level_t _level, const char* _buf) // FIXME use level
{
g_log << Logger::Error << _buf << endl;
}

void renderCard(std::vector<std::unique_ptr<UIEntity>>& uielements, nlohmann::basic_json<>& card)
static lv_obj_t* clocklabel{nullptr};

void renderCard(std::vector<std::unique_ptr<UIEntity>>& _uielements, nlohmann::basic_json<>& _card, lv_obj_t* _parent)
try {
if (card["type"] == "entities") {
if (card.contains("entities")) { // array of objects with the entity name in it.
auto objs = card["entities"];
if (_card["type"] == "entities") {
if (_card.contains("entities")) { // array of objects with the entity name in it.
auto objs = _card["entities"];
for (auto ent : objs) {
string entityname;
string icon;
Expand All @@ -100,67 +85,84 @@ try {
}
std::shared_ptr<HAEntity> entity = HABackend::getInstance().getEntityByName(entityname);
if (entity->getEntityType() == EntityType::Light) {
std::unique_ptr<UIEntity> btn = std::make_unique<UISwitch>(entity, cont_row);
uielements.push_back(std::move(btn));
std::unique_ptr<UIEntity> btn = std::make_unique<UISwitch>(entity, _parent);
_uielements.push_back(std::move(btn));
}
else if (entity->getEntityType() == EntityType::Switch) {
std::unique_ptr<UIEntity> btn = std::make_unique<UISwitch>(entity, cont_row);
uielements.push_back(std::move(btn));
std::unique_ptr<UIEntity> btn = std::make_unique<UISwitch>(entity, _parent);
_uielements.push_back(std::move(btn));
}
else if (entity->getEntityType() == EntityType::Sensor || true) {
// we used to fall back to UIDummy, but UISensor actually shows things better than that,
// hence the || true. If you add a type, put it above.
std::unique_ptr<UIEntity> sensor = std::make_unique<UISensor>(entity, cont_row, icon);
uielements.push_back(std::move(sensor));
std::unique_ptr<UIEntity> sensor = std::make_unique<UISensor>(entity, _parent, icon);
_uielements.push_back(std::move(sensor));
}
}
}
}
else if (card["type"] == "button") {
if (card.contains("entity")) {
string entityname = card["entity"];
else if (_card["type"] == "button") {
if (_card.contains("entity")) {
string entityname = _card["entity"];
std::shared_ptr<HAEntity> entity = HABackend::getInstance().getEntityByName(entityname);
std::unique_ptr<UIEntity> btn = std::make_unique<UIButton>(entity, cont_row);
uielements.push_back(std::move(btn));
std::unique_ptr<UIEntity> btn = std::make_unique<UIButton>(entity, _parent);
_uielements.push_back(std::move(btn));
}
else {
g_log << Logger::Warning << "Card is of type button, but no entity found: " << card << std::endl;
g_log << Logger::Warning << "Card is of type button, but no entity found: " << _card << std::endl;
}
}
else if (card["type"] == "light") {
if (card.contains("entity")) {
string entityname = card["entity"];
else if (_card["type"] == "light") {
if (_card.contains("entity")) {
string entityname = _card["entity"];
std::shared_ptr<HAEntity> entity = HABackend::getInstance().getEntityByName(entityname);
std::unique_ptr<UIEntity> btn = std::make_unique<UIRGBLight>(entity, cont_row);
uielements.push_back(std::move(btn));
std::unique_ptr<UIEntity> btn = std::make_unique<UIRGBLight>(entity, _parent);
_uielements.push_back(std::move(btn));
}
else {
g_log << Logger::Warning << "Card is of type button, but no entity found: " << card << std::endl;
g_log << Logger::Warning << "Card is of type light, but no entity found: " << _card << std::endl;
}
}
else if (card["type"] == "custom:apexcharts-card") {
else if (_card["type"] == "custom:apexcharts-card") {

std::unique_ptr<UIEntity> apex = std::make_unique<UIApexCard>(card, cont_row);
uielements.push_back(std::move(apex));
std::unique_ptr<UIEntity> apex = std::make_unique<UIApexCard>(_card, _parent);
_uielements.push_back(std::move(apex));
g_log << Logger::Warning << "got apex card" << std::endl;
}
else {
if (card.contains(("entity"))) {
g_log << Logger::Warning << "Card of type " << card["type"] << " found, but we have no matching UIEntity. Falling back to 'sensor' for entity." << card["entity"] << std::endl;
string entityname = card["entity"];
if (_card.contains(("entity"))) {
g_log << Logger::Warning << "Card of type " << _card["type"] << " found, but we have no matching UIEntity. Falling back to 'sensor' for entity." << _card["entity"] << std::endl;
string entityname = _card["entity"];
std::shared_ptr<HAEntity> entity = HABackend::getInstance().getEntityByName(entityname);
std::unique_ptr<UIEntity> sensor = std::make_unique<UISensor>(entity, cont_row);
uielements.push_back(std::move(sensor));
std::unique_ptr<UIEntity> sensor = std::make_unique<UISensor>(entity, _parent);
_uielements.push_back(std::move(sensor));
}
else {
g_log << Logger::Warning << "Card of type " << card["type"] << " found, couldn't find entity." << std::endl;
g_log << Logger::Warning << "Card of type " << _card["type"] << " found, couldn't find entity." << std::endl;
}
}
}
catch (std::out_of_range) { // this does not really discriminate for exception origin, but it makes us not crash on disabled things.
return;
}

void renderSection(std::vector<std::unique_ptr<UIEntity>>& _uielements, nlohmann::basic_json<>& _section, lv_obj_t* _parent)
{
auto section = lv_obj_create(_parent);
lv_obj_remove_style_all(section);
lv_obj_set_height(section, lv_pct(100));
lv_obj_set_flex_flow(section, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_grow(section, 1);
lv_obj_set_style_pad_row(section, 5, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_column(section, 9, LV_PART_MAIN | LV_STATE_DEFAULT);
// lv_obj_set_scroll_snap_x(section, LV_SCROLL_SNAP_START);
lv_obj_remove_flag(section, LV_OBJ_FLAG_SCROLLABLE);

for (auto card : _section["cards"]) {
renderCard(_uielements, card, section);
}
}

void uithread(int _argc, char* _argv[])
{
argparse::ArgumentParser program("voorkant-lvgl", getVersion());
Expand Down Expand Up @@ -301,13 +303,9 @@ void uithread(int _argc, char* _argv[])
/*Create a container with ROW flex direction that wraps.
This is our MAIN box that we put everything in except logs. We have this here because we want some spacing around it.
*/
cont_row = lv_obj_create(row_and_logs);
static auto cont_row = lv_obj_create(row_and_logs);
lv_obj_remove_style_all(cont_row);
lv_obj_set_size(cont_row, lv_pct(100), lv_pct(80));
lv_obj_set_flex_flow(cont_row, LV_FLEX_FLOW_COLUMN_WRAP);
lv_obj_set_style_pad_row(cont_row, 5, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_column(cont_row, 9, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_scroll_snap_x(cont_row, LV_SCROLL_SNAP_START);

/* Bottom row */
lv_obj_t* bottom_row = lv_obj_create(row_and_logs);
Expand All @@ -323,24 +321,12 @@ void uithread(int _argc, char* _argv[])
lv_style_set_radius(&style, lv_coord_t(0));
lv_obj_add_style(bottom_row, &style, 0);

lv_obj_t* left_btn = lv_button_create(bottom_row);
lv_obj_t* left_btn_txt = lv_label_create(left_btn);
lv_label_set_text(left_btn_txt, voorkant::mdi::name2id("arrow-left").data());
lv_obj_add_event_cb(left_btn, btnLeftPress, LV_EVENT_CLICKED, NULL);
lv_obj_add_style(left_btn, &voorkant::lvgl::mdistyle, 0);

UILogBox logbox(bottom_row, &voorkant::lvgl::b612style);

lv_obj_t* right_btn = lv_button_create(bottom_row);
lv_obj_t* right_btn_txt = lv_label_create(right_btn);
lv_label_set_text(right_btn_txt, voorkant::mdi::name2id("arrow-right").data());
lv_obj_add_style(right_btn_txt, &voorkant::lvgl::mdistyle, 0);

lv_obj_add_event_cb(right_btn, btnRightPress, LV_EVENT_CLICKED, NULL);

lv_log_register_print_cb(lvLogCallback);

std::vector<std::unique_ptr<UIEntity>> uielements;
std::vector<lv_obj_t*> viewtabs;
if (program.is_subcommand_used(entity_command) || program.is_subcommand_used(dashboard_command)) {
// need to collect some data used in both cases

Expand Down Expand Up @@ -428,20 +414,63 @@ void uithread(int _argc, char* _argv[])

// FIXME: lots of repeat code here, should do a <template> thing
json result = doc["result"];

static auto tabview = lv_tabview_create(cont_row);

lv_obj_add_style(tabview, &voorkant::lvgl::b612style, 0); // FIXME: this should not be necessary?
lv_obj_remove_flag(lv_tabview_get_content(tabview), LV_OBJ_FLAG_SCROLLABLE);

lv_obj_t* tab_bar = lv_tabview_get_tab_bar(tabview);
lv_obj_set_width(tab_bar, LV_PCT(80)); // FIXME: if you set this to 100, or remove the entire line, the tabbar renders wrong until it gets touched. Sometimes also happens at 80....

// FIXME: don't use a tab bar at all if there is exactly one view?
for (auto view : result["views"]) {
uint32_t idx = lv_obj_get_child_count(lv_tabview_get_tab_bar(tabview));

auto title = view["title"].get<std::string>();
auto tab = lv_tabview_add_tab(tabview, title.c_str());

lv_obj_t* button = lv_obj_get_child_by_type(tab_bar, idx, &lv_button_class);
lv_obj_set_flex_grow(button, 0);
lv_obj_set_width(button, LV_SIZE_CONTENT);
lv_obj_t* label = lv_obj_get_child_by_type(button, 0, &lv_label_class);
lv_obj_set_style_margin_hor(label, 10, 0);

if (view.count("icon")) {
// next couple of lines taken from lg_tabview.c lv_tabview_rename_tab, suggesting that an accessor is missing
lv_label_set_text(label, voorkant::mdi::name2id(view["icon"].get<std::string>().substr(4)).data());
lv_obj_add_style(label, &voorkant::lvgl::mdistyle, 0);
}
viewtabs.push_back(tab);
lv_obj_remove_style_all(tab);
lv_obj_set_size(tab, lv_pct(100), lv_pct(100));
lv_obj_set_flex_flow(tab, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_row(tab, 5, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_column(tab, 9, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_scroll_snap_x(tab, LV_SCROLL_SNAP_START);
lv_obj_remove_flag(tab, LV_OBJ_FLAG_SCROLLABLE);

if (view.contains("sections")) {
for (auto section : view["sections"]) {
for (auto card : section["cards"]) {
renderCard(uielements, card);
}
renderSection(uielements, section, tab);
}
}
if (view.contains("cards")) {
for (auto card : view["cards"]) {
renderCard(uielements, card);
}
else {
// if we get here, this was not a Sections view, and we dumbly assume it's a masonry view, holding cards
// FIXME: which we then all render vertically as if they are in one section, which is not pretty
// need to check whether type 3 (Panel) and 4 (Sidebar) from https://www.home-assistant.io/dashboards/views/ do anything sensible here
renderSection(uielements, view, tab);
}
}

// FIXME: nothing about the way this clock is put on the screen makes me happy. I want it to be part of tab_bar, flushed to the right
clocklabel = lv_label_create(lv_screen_active());
lv_obj_set_align(clocklabel, LV_ALIGN_TOP_RIGHT);
lv_label_set_text(clocklabel, "13:37");
voorkant::lvgl::b612fontbig = lv_tiny_ttf_create_data_ex(B612_Regular_ttf, B612_Regular_ttf_len, 32, LV_FONT_KERNING_NORMAL, 1024);
lv_style_init(&voorkant::lvgl::b612stylebig);
lv_style_set_text_font(&voorkant::lvgl::b612stylebig, voorkant::lvgl::b612fontbig);
lv_obj_add_style(clocklabel, &voorkant::lvgl::b612stylebig, 0);
}
else {
g_log << Logger::Info << "We expected a command" << std::endl;
Expand All @@ -458,6 +487,12 @@ void uithread(int _argc, char* _argv[])
usleep(5 * 1000); // 5000 usec = 5 ms
{
std::unique_lock<std::mutex> lvlock(g_lvgl_updatelock);
// FIXME: we don't need to update the clock every 5ms
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
std::ostringstream time;
time << std::put_time(&tm, "%H:%M:%S");
lv_label_set_text(clocklabel, time.str().c_str());
lv_tick_inc(5); // 5 ms
lv_task_handler();
}
Expand Down
21 changes: 13 additions & 8 deletions src/uicomponents/UIComponents.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ UIButton::UIButton(std::shared_ptr<HAEntity> _entity, lv_obj_t* _parent) :
{
// FIXME: This code is duplicated in UISwitch, consider this after another few UIentities
btn = lv_button_create(_parent);
lv_obj_set_size(btn, uiEntityWidth, 50);
// lv_obj_set_size(btn, uiEntityWidth, 50);
lv_obj_set_width(btn, LV_PCT(100));

lv_obj_center(btn);
lv_obj_set_style_pad_all(btn, 5, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_add_flag(btn, LV_OBJ_FLAG_CHECKABLE);
Expand Down Expand Up @@ -90,8 +92,9 @@ UISwitch::UISwitch(std::shared_ptr<HAEntity> _entity, lv_obj_t* _parent) :
{
// This code is duplicated in UISwitch, consider this after another few UIentities
lv_obj_t* switchcontainer = lv_obj_create(_parent);
lv_obj_set_width(switchcontainer, uiEntityWidth);
// lv_obj_set_width(switchcontainer, uiEntityWidth);
lv_obj_set_height(switchcontainer, LV_SIZE_CONTENT);
lv_obj_set_width(switchcontainer, LV_PCT(100));
lv_obj_remove_flag(switchcontainer, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_style_pad_all(switchcontainer, 5, LV_PART_MAIN | LV_STATE_DEFAULT);

Expand All @@ -100,9 +103,9 @@ UISwitch::UISwitch(std::shared_ptr<HAEntity> _entity, lv_obj_t* _parent) :
lv_obj_add_event_cb(sw, UISwitch::swToggleCB, LV_EVENT_VALUE_CHANGED, reinterpret_cast<void*>(&entity));
lv_obj_set_align(sw, LV_ALIGN_RIGHT_MID);

lv_obj_t* label = createLabel(switchcontainer, _entity->name);
lv_obj_t* label = createLabel(switchcontainer, _entity->name); // FIXME _entity->name ignores custom names set in dashboard. likely a problem in more places.
lv_obj_set_align(label, LV_ALIGN_LEFT_MID);
lv_obj_set_width(label, uiEntityWidth - 65); // 50 + padding == switch width
// lv_obj_set_width(label, uiEntityWidth - 65); // 50 + padding == switch width

update();
};
Expand All @@ -127,8 +130,8 @@ void UISwitch::swToggleCB(lv_event_t* _e)

std::shared_ptr<HAEntity> ent = *reinterpret_cast<std::shared_ptr<HAEntity>*>(lv_event_get_user_data(_e));
if (code == LV_EVENT_VALUE_CHANGED) {
HADomains::Light light(ent);
light.toggle({});
HADomains::Light light(ent); // FIXME: this is wrong for things like switch.ac, sends the wrong domain (light instead of switch)
light.toggle({}); // FIXME: HAweb explicitly calls turn_on/turn_off instead of toggle
}
};

Expand Down Expand Up @@ -177,7 +180,8 @@ UISensor::UISensor(std::shared_ptr<HAEntity> _entity, lv_obj_t* _parent, std::st
UIEntity(_entity, _parent)
{
lv_obj_t* flowpanel = lv_obj_create(_parent);
lv_obj_set_width(flowpanel, uiEntityWidth);
// lv_obj_set_width(flowpanel, uiEntityWidth);
lv_obj_set_width(flowpanel, LV_PCT(100));
lv_obj_set_height(flowpanel, LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(flowpanel, 5, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_align(flowpanel, LV_ALIGN_CENTER);
Expand Down Expand Up @@ -205,7 +209,8 @@ UISensor::UISensor(std::shared_ptr<HAEntity> _entity, lv_obj_t* _parent, std::st
lv_obj_set_style_text_align(iconpart, LV_TEXT_ALIGN_CENTER, 0);

lv_obj_t* textpart = lv_obj_create(flowpanel);
lv_obj_set_width(textpart, uiEntityWidth - 55);
// lv_obj_set_width(textpart, uiEntityWidth - 55);
lv_obj_set_flex_grow(textpart, 1);
lv_obj_set_height(textpart, LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(textpart, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_border_width(textpart, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
Expand Down
3 changes: 2 additions & 1 deletion src/uicomponents/UIComponents.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ class UIEntity : public UIComponent

protected:
std::shared_ptr<HAEntity> entity;
const lv_coord_t uiEntityWidth = (800 - (2 * 9)) / 3;
// const lv_coord_t uiEntityWidth = (800 - (2 * 9)) / 3;
const lv_coord_t uiEntityWidth = 200;
};

class UIButton : public UIEntity
Expand Down
2 changes: 1 addition & 1 deletion src/uicomponents/UILogBox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ UILogBox::UILogBox(lv_obj_t* _parent, lv_style_t* _style) :
lv_obj_set_flex_align(log_box, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START);
lv_obj_set_flex_grow(log_box, 1);
lv_textarea_set_placeholder_text(log_box, "No log...");
lv_obj_add_style(log_box, _style, 0);
lv_obj_add_style(log_box, _style, 0); // FIXME: this line is confirmed unnecessary
g_log.attach((IObserver*)this);
}

Expand Down
Loading
Loading