mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-09-01 16:48:23 +00:00
feat(ui): add app launcher and NomadNet MVP
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
#include "NomadNetScreen.h"
|
||||
#ifdef ARDUINO
|
||||
#include "Theme.h"
|
||||
#include "../LVGL/LVGLInit.h"
|
||||
#include "../TextAreaHelper.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace UI::LXMF {
|
||||
NomadNetScreen::NomadNetScreen() {
|
||||
_screen=lv_obj_create(lv_scr_act()); lv_obj_set_size(_screen,320,240);
|
||||
lv_obj_set_style_bg_color(_screen,Theme::surface(),0); lv_obj_set_style_border_width(_screen,0,0); lv_obj_set_style_pad_all(_screen,0,0);
|
||||
lv_obj_t* header=lv_obj_create(_screen); lv_obj_set_size(header,LV_PCT(100),34); lv_obj_align(header,LV_ALIGN_TOP_MID,0,0);
|
||||
lv_obj_set_style_bg_color(header,Theme::surfaceHeader(),0); lv_obj_set_style_border_width(header,0,0); lv_obj_set_style_pad_all(header,2,0);
|
||||
_back_button=lv_btn_create(header); lv_obj_set_size(_back_button,38,28); lv_obj_align(_back_button,LV_ALIGN_LEFT_MID,0,0);
|
||||
lv_obj_t* bl=lv_label_create(_back_button);lv_label_set_text(bl,LV_SYMBOL_LEFT);lv_obj_center(bl);
|
||||
_home_button=lv_btn_create(header);lv_obj_set_size(_home_button,38,28);lv_obj_align(_home_button,LV_ALIGN_LEFT_MID,42,0);
|
||||
lv_obj_t* hl=lv_label_create(_home_button);lv_label_set_text(hl,LV_SYMBOL_HOME);lv_obj_center(hl);
|
||||
lv_obj_t* title=lv_label_create(header);lv_label_set_text(title,"NomadNet");lv_obj_center(title);
|
||||
_reload_button=lv_btn_create(header);lv_obj_set_size(_reload_button,42,28);lv_obj_align(_reload_button,LV_ALIGN_RIGHT_MID,0,0);
|
||||
lv_obj_t* rl=lv_label_create(_reload_button);lv_label_set_text(rl,LV_SYMBOL_REFRESH);lv_obj_center(rl);
|
||||
lv_obj_t* address_row=lv_obj_create(_screen);lv_obj_set_size(address_row,LV_PCT(100),38);lv_obj_align(address_row,LV_ALIGN_TOP_MID,0,34);
|
||||
lv_obj_set_style_bg_color(address_row,Theme::surface(),0);lv_obj_set_style_border_width(address_row,0,0);lv_obj_set_style_pad_all(address_row,3,0);
|
||||
_address=lv_textarea_create(address_row);lv_obj_set_size(_address,270,30);lv_obj_align(_address,LV_ALIGN_LEFT_MID,0,0);
|
||||
lv_textarea_set_one_line(_address,true);lv_textarea_set_max_length(_address,511);lv_textarea_set_placeholder_text(_address,"32-hex-destination:/page/path");
|
||||
lv_obj_set_style_text_font(_address,&lv_font_montserrat_12,0);TextAreaHelper::enable_paste(_address);
|
||||
_go_button=lv_btn_create(address_row);lv_obj_set_size(_go_button,42,30);lv_obj_align(_go_button,LV_ALIGN_RIGHT_MID,0,0);
|
||||
lv_obj_t* gl=lv_label_create(_go_button);lv_label_set_text(gl,"Go");lv_obj_center(gl);
|
||||
_status=lv_label_create(_screen);lv_obj_set_size(_status,312,18);lv_obj_align(_status,LV_ALIGN_TOP_LEFT,4,72);
|
||||
lv_obj_set_style_text_font(_status,&lv_font_montserrat_12,0);lv_obj_set_style_text_color(_status,Theme::textTertiary(),0);
|
||||
_content=lv_obj_create(_screen);lv_obj_set_size(_content,320,150);lv_obj_align(_content,LV_ALIGN_BOTTOM_MID,0,0);
|
||||
lv_obj_set_style_bg_color(_content,Theme::surface(),0);lv_obj_set_style_border_width(_content,0,0);lv_obj_set_style_pad_all(_content,6,0);
|
||||
lv_obj_set_flex_flow(_content,LV_FLEX_FLOW_COLUMN);lv_obj_set_flex_align(_content,LV_FLEX_ALIGN_START,LV_FLEX_ALIGN_START,LV_FLEX_ALIGN_START);
|
||||
lv_obj_set_scroll_dir(_content,LV_DIR_VER);lv_obj_set_scrollbar_mode(_content,LV_SCROLLBAR_MODE_AUTO);
|
||||
for(auto* o:{_back_button,_home_button,_reload_button,_go_button})lv_obj_add_event_cb(o,clicked,LV_EVENT_CLICKED,this);
|
||||
lv_obj_add_event_cb(_address,clicked,LV_EVENT_READY,this);
|
||||
set_status("Enter a NomadNet address");hide();
|
||||
}
|
||||
NomadNetScreen::~NomadNetScreen(){if(_screen)lv_obj_del(_screen);}
|
||||
void NomadNetScreen::set_address(const std::string& value){lv_textarea_set_text(_address,value.c_str());}
|
||||
std::string NomadNetScreen::address()const{return lv_textarea_get_text(_address);}
|
||||
void NomadNetScreen::set_status(const char* value){lv_label_set_text(_status,value?value:"");}
|
||||
void NomadNetScreen::set_page(const NomadNet::Document& document) {
|
||||
auto* group = LVGL::LVGLInit::get_default_group();
|
||||
if (group) for (auto* object : _focusables) lv_group_remove_obj(object);
|
||||
lv_obj_clean(_content);
|
||||
_focusables.clear();
|
||||
_link_targets.clear();
|
||||
std::size_t objects = 0;
|
||||
std::size_t spans = 0;
|
||||
bool render_truncated = false;
|
||||
lv_obj_set_style_bg_color(_content, document.has_background
|
||||
? lv_color_hex(document.background) : Theme::surface(), 0);
|
||||
|
||||
for (const auto& block : document.blocks) {
|
||||
if (objects >= MAX_UI_OBJECTS || spans >= MAX_UI_SPANS) {
|
||||
render_truncated = true;
|
||||
break;
|
||||
}
|
||||
if (block.type == NomadNet::BlockType::DIVIDER) {
|
||||
lv_obj_t* line = lv_obj_create(_content);
|
||||
lv_obj_set_size(line, 304, 1);
|
||||
lv_obj_set_style_bg_color(line, Theme::border(), 0);
|
||||
lv_obj_set_style_border_width(line, 0, 0);
|
||||
++objects;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Spans preserve mixed inline styling and wrap the complete source line
|
||||
// together instead of introducing breaks at every style transition.
|
||||
lv_obj_t* text = lv_spangroup_create(_content);
|
||||
const lv_coord_t indent = block.type == NomadNet::BlockType::HEADING
|
||||
? 0 : static_cast<lv_coord_t>(std::min<unsigned>(block.depth, 8) * 4);
|
||||
lv_obj_set_width(text, 304 - indent);
|
||||
lv_obj_set_style_translate_x(text, indent, 0);
|
||||
lv_spangroup_set_mode(text, LV_SPAN_MODE_BREAK);
|
||||
lv_spangroup_set_overflow(text, LV_SPAN_OVERFLOW_CLIP);
|
||||
lv_spangroup_set_lines(text, -1);
|
||||
lv_spangroup_set_align(text, block.alignment == NomadNet::Alignment::CENTER
|
||||
? LV_TEXT_ALIGN_CENTER : block.alignment == NomadNet::Alignment::RIGHT
|
||||
? LV_TEXT_ALIGN_RIGHT : LV_TEXT_ALIGN_LEFT);
|
||||
++objects;
|
||||
|
||||
for (const auto& run : block.runs) {
|
||||
if (spans >= MAX_UI_SPANS) { render_truncated = true; break; }
|
||||
lv_span_t* span = lv_spangroup_new_span(text);
|
||||
lv_span_set_text(span, run.text.c_str());
|
||||
const bool is_link = run.link_index >= 0 &&
|
||||
static_cast<std::size_t>(run.link_index) < document.links.size();
|
||||
lv_style_set_text_color(&span->style, is_link ? Theme::primaryLight() :
|
||||
run.has_foreground ? lv_color_hex(run.foreground) :
|
||||
document.has_foreground ? lv_color_hex(document.foreground) : Theme::textPrimary());
|
||||
lv_style_set_text_font(&span->style,
|
||||
(run.bold || block.type == NomadNet::BlockType::HEADING)
|
||||
? &lv_font_montserrat_16 : &lv_font_montserrat_12);
|
||||
lv_style_set_text_line_space(&span->style, 3);
|
||||
if (run.underline || is_link)
|
||||
lv_style_set_text_decor(&span->style, LV_TEXT_DECOR_UNDERLINE);
|
||||
if (run.italic) lv_style_set_text_letter_space(&span->style, 1);
|
||||
if (run.has_background) lv_style_set_bg_color(&span->style, lv_color_hex(run.background));
|
||||
++spans;
|
||||
}
|
||||
lv_spangroup_refr_mode(text);
|
||||
|
||||
// A visual span cannot itself participate in the encoder focus group.
|
||||
// Add one bounded, focusable activation control for every rendered link.
|
||||
for (const auto& run : block.runs) {
|
||||
if (run.link_index < 0 || static_cast<std::size_t>(run.link_index) >= document.links.size()) continue;
|
||||
if (objects + 2 > MAX_UI_OBJECTS) { render_truncated = true; break; }
|
||||
lv_obj_t* button = lv_btn_create(_content);
|
||||
lv_obj_set_size(button, 304, 28);
|
||||
lv_obj_set_style_pad_all(button, 4, 0);
|
||||
lv_obj_t* label = lv_label_create(button);
|
||||
const std::string caption = "Open: " + run.text;
|
||||
lv_label_set_text(label, caption.c_str());
|
||||
lv_label_set_long_mode(label, LV_LABEL_LONG_DOT);
|
||||
lv_obj_set_width(label, 294);
|
||||
lv_obj_center(label);
|
||||
_link_targets.push_back(document.links[run.link_index].target);
|
||||
lv_obj_set_user_data(button, reinterpret_cast<void*>(_link_targets.size()));
|
||||
lv_obj_add_event_cb(button, clicked, LV_EVENT_CLICKED, this);
|
||||
_focusables.push_back(button);
|
||||
if (_visible && group) lv_group_add_obj(group, button);
|
||||
objects += 2;
|
||||
}
|
||||
}
|
||||
if (document.truncated || render_truncated) {
|
||||
lv_obj_t* notice = lv_label_create(_content);
|
||||
lv_label_set_text(notice, "[Page truncated to device safety limits]");
|
||||
lv_obj_set_style_text_color(notice, Theme::warning(), 0);
|
||||
} else if (document.unsupported) {
|
||||
lv_obj_t* notice = lv_label_create(_content);
|
||||
lv_label_set_text(notice, "Forms, partials, tables and downloads are unsupported in this MVP.");
|
||||
lv_obj_set_style_text_color(notice, Theme::warning(), 0);
|
||||
}
|
||||
}
|
||||
void NomadNetScreen::show(){_visible=true;lv_obj_clear_flag(_screen,LV_OBJ_FLAG_HIDDEN);lv_obj_move_foreground(_screen);auto*g=LVGL::LVGLInit::get_default_group();if(g){for(auto*o:{_back_button,_home_button,_reload_button,_address,_go_button})lv_group_add_obj(g,o);for(auto*o:_focusables)lv_group_add_obj(g,o);lv_group_focus_obj(_address);}}
|
||||
void NomadNetScreen::hide(){_visible=false;auto*g=LVGL::LVGLInit::get_default_group();if(g){for(auto*o:{_back_button,_home_button,_reload_button,_address,_go_button})lv_group_remove_obj(o);for(auto*o:_focusables)lv_group_remove_obj(o);}lv_obj_add_flag(_screen,LV_OBJ_FLAG_HIDDEN);}
|
||||
void NomadNetScreen::clicked(lv_event_t*e){auto*s=static_cast<NomadNetScreen*>(lv_event_get_user_data(e));auto*t=lv_event_get_target(e);if(t==s->_back_button&&s->_back)s->_back();else if(t==s->_home_button&&s->_home)s->_home();else if(t==s->_reload_button&&s->_reload)s->_reload();else if((t==s->_go_button||t==s->_address)&&s->_open)s->_open(s->address());else{std::size_t n=reinterpret_cast<std::size_t>(lv_obj_get_user_data(t));if(n>0&&n<=s->_link_targets.size()&&s->_link)s->_link(s->_link_targets[n-1]);}}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user