mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-09-24 12:44:21 +00:00
211 lines
6.9 KiB
C++
211 lines
6.9 KiB
C++
#include "lvgl.h"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
namespace ui::page_profile
|
|
{
|
|
struct Profile
|
|
{
|
|
lv_coord_t top_bar_height = 40;
|
|
};
|
|
struct Size
|
|
{
|
|
lv_coord_t width, height;
|
|
};
|
|
const Profile& current()
|
|
{
|
|
static Profile profile;
|
|
return profile;
|
|
}
|
|
Size resolve_modal_size(lv_coord_t w, lv_coord_t h, lv_obj_t*) { return {w, h}; }
|
|
lv_coord_t resolve_modal_pad() { return 4; }
|
|
} // namespace ui::page_profile
|
|
namespace ui::widgets
|
|
{
|
|
constexpr int kTopBarHeight = 40;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
namespace style
|
|
{
|
|
void apply_modal_bg(lv_obj_t*) {}
|
|
void apply_modal_panel(lv_obj_t*) {}
|
|
} // namespace style
|
|
struct State
|
|
{
|
|
lv_obj_t* root = nullptr;
|
|
struct
|
|
{
|
|
lv_obj_t* back_btn = nullptr;
|
|
} top_bar;
|
|
lv_obj_t* modal_root = nullptr;
|
|
lv_group_t* modal_group = nullptr;
|
|
lv_obj_t* modal_textarea = nullptr;
|
|
lv_obj_t* modal_error = nullptr;
|
|
void* editing_item = nullptr;
|
|
void* editing_widget = nullptr;
|
|
} g_state;
|
|
struct ImeFixture
|
|
{
|
|
void detach() {}
|
|
};
|
|
std::unique_ptr<ImeFixture> s_text_modal_ime;
|
|
lv_group_t* s_modal_prev_group = nullptr;
|
|
lv_group_t* page_group = nullptr;
|
|
lv_obj_t* page_root = nullptr;
|
|
lv_obj_t* page_item = nullptr;
|
|
lv_obj_t* s_gps_diagnostics_label = nullptr;
|
|
lv_obj_t* s_spi_diagnostics_label = nullptr;
|
|
std::array<lv_obj_t*, 6> s_manual_time_rollers{};
|
|
std::array<lv_obj_t*, 8> s_manual_datetime_focus_order{};
|
|
unsigned s_manual_datetime_focus_count = 0, s_option_click_count = 0, s_ime_toggle_count = 0;
|
|
unsigned refresh_count = 0, exit_count = 0;
|
|
|
|
void set_default_group(lv_group_t* group)
|
|
{
|
|
for (auto* input = lv_indev_get_next(nullptr); input; input = lv_indev_get_next(input))
|
|
lv_indev_set_group(input, group);
|
|
lv_group_set_default(group);
|
|
}
|
|
namespace settings::ui::input
|
|
{
|
|
lv_group_t* get_group() { return page_group; }
|
|
void on_ui_refreshed()
|
|
{
|
|
++refresh_count;
|
|
lv_group_remove_all_objs(page_group);
|
|
lv_group_add_obj(page_group, g_state.top_bar.back_btn);
|
|
lv_group_add_obj(page_group, page_item);
|
|
lv_group_focus_obj(page_item);
|
|
}
|
|
} // namespace settings::ui::input
|
|
void ui_request_exit_to_menu() { ++exit_count; }
|
|
#include "settings_lifecycle_actual.inc"
|
|
|
|
void tick()
|
|
{
|
|
lv_tick_inc(20);
|
|
lv_timer_handler();
|
|
}
|
|
void open_modal(bool text)
|
|
{
|
|
modal_prepare_group();
|
|
assert(lv_obj_get_group(g_state.top_bar.back_btn) == g_state.modal_group);
|
|
g_state.modal_root = create_modal_root(300, 240);
|
|
auto* panel = lv_obj_get_child(g_state.modal_root, 0);
|
|
auto* field = text ? lv_textarea_create(panel) : lv_button_create(panel);
|
|
lv_group_add_obj(g_state.modal_group, field);
|
|
lv_obj_add_event_cb(field, on_modal_key, LV_EVENT_KEY, nullptr);
|
|
lv_group_focus_obj(field);
|
|
if (text)
|
|
{
|
|
g_state.modal_textarea = field;
|
|
s_text_modal_ime = std::make_unique<ImeFixture>();
|
|
}
|
|
lv_group_set_editing(g_state.modal_group, false);
|
|
lv_obj_update_layout(page_root);
|
|
assert(lv_obj_get_y(g_state.modal_root) == 40);
|
|
assert(lv_obj_get_height(g_state.modal_root) == 200);
|
|
assert(lv_obj_get_height(panel) <= 200);
|
|
assert(lv_obj_get_height(page_item) == 200);
|
|
}
|
|
} // namespace
|
|
|
|
int main()
|
|
{
|
|
lv_init();
|
|
auto* display = lv_display_create(320, 240);
|
|
lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565);
|
|
std::vector<uint16_t> pixels(320 * 240);
|
|
lv_display_set_buffers(display, pixels.data(), nullptr, pixels.size() * 2, LV_DISPLAY_RENDER_MODE_FULL);
|
|
lv_display_set_flush_cb(display, [](lv_display_t* d, const lv_area_t*, uint8_t*)
|
|
{ lv_display_flush_ready(d); });
|
|
page_root = lv_obj_create(lv_screen_active());
|
|
g_state.root = page_root;
|
|
lv_obj_remove_style_all(page_root);
|
|
lv_obj_set_size(page_root, 320, 240);
|
|
lv_obj_set_flex_flow(page_root, LV_FLEX_FLOW_COLUMN);
|
|
lv_obj_clear_flag(page_root, LV_OBJ_FLAG_SCROLLABLE);
|
|
page_group = lv_group_create();
|
|
set_default_group(page_group);
|
|
auto* header = lv_obj_create(page_root);
|
|
lv_obj_remove_style_all(header);
|
|
lv_obj_set_size(header, 320, 40);
|
|
g_state.top_bar.back_btn = lv_button_create(header);
|
|
lv_obj_set_pos(g_state.top_bar.back_btn, 0, 0);
|
|
lv_obj_set_size(g_state.top_bar.back_btn, 60, 32);
|
|
lv_obj_add_event_cb(
|
|
g_state.top_bar.back_btn, [](lv_event_t*)
|
|
{ settings_back_cb(nullptr); },
|
|
LV_EVENT_CLICKED, nullptr);
|
|
page_item = lv_button_create(page_root);
|
|
lv_obj_set_size(page_item, 320, 0);
|
|
lv_obj_set_flex_grow(page_item, 1);
|
|
|
|
lv_indev_data_t encoder_sample{}, pointer_sample{};
|
|
encoder_sample.key = LV_KEY_ENTER;
|
|
auto* encoder = lv_indev_create();
|
|
lv_indev_set_type(encoder, LV_INDEV_TYPE_ENCODER);
|
|
lv_indev_set_user_data(encoder, &encoder_sample);
|
|
auto* pointer = lv_indev_create();
|
|
lv_indev_set_type(pointer, LV_INDEV_TYPE_POINTER);
|
|
lv_indev_set_user_data(pointer, &pointer_sample);
|
|
for (auto* input : {encoder, pointer})
|
|
{
|
|
lv_indev_set_display(input, display);
|
|
lv_indev_set_read_cb(input, [](lv_indev_t* device, lv_indev_data_t* data)
|
|
{ *data = *static_cast<lv_indev_data_t*>(lv_indev_get_user_data(device)); });
|
|
}
|
|
auto read = [](lv_indev_t* input)
|
|
{ lv_tick_inc(20); lv_indev_read(input); };
|
|
for (int cycle = 0; cycle < 100; ++cycle)
|
|
{
|
|
open_modal(cycle % 2 == 0);
|
|
for (unsigned step = 0; step < 8 && lv_group_get_focused(g_state.modal_group) != g_state.top_bar.back_btn; ++step)
|
|
{
|
|
encoder_sample.enc_diff = 1;
|
|
read(encoder);
|
|
}
|
|
encoder_sample.enc_diff = 0;
|
|
assert(lv_group_get_focused(g_state.modal_group) == g_state.top_bar.back_btn);
|
|
encoder_sample.state = LV_INDEV_STATE_PRESSED;
|
|
read(encoder);
|
|
encoder_sample.state = LV_INDEV_STATE_RELEASED;
|
|
read(encoder);
|
|
assert(!g_state.modal_root && !g_state.modal_group && !s_text_modal_ime);
|
|
assert(exit_count == 0);
|
|
assert(lv_indev_get_group(encoder) == page_group);
|
|
assert(lv_obj_get_group(g_state.top_bar.back_btn) == page_group);
|
|
tick();
|
|
|
|
open_modal(cycle % 2 != 0);
|
|
pointer_sample.point = {20, 15};
|
|
pointer_sample.state = LV_INDEV_STATE_PRESSED;
|
|
read(pointer);
|
|
pointer_sample.state = LV_INDEV_STATE_RELEASED;
|
|
read(pointer);
|
|
assert(!g_state.modal_root && exit_count == 0);
|
|
tick();
|
|
}
|
|
settings_back_cb(nullptr);
|
|
assert(exit_count == 1);
|
|
|
|
// Destroying the page must not rebuild its focus group along the way.
|
|
open_modal(true);
|
|
const auto before = refresh_count;
|
|
modal_close(false);
|
|
assert(refresh_count == before && !s_modal_prev_group);
|
|
set_default_group(nullptr);
|
|
lv_group_delete(page_group);
|
|
lv_obj_delete(page_root);
|
|
tick(); // Pending child deletion must be cancelled by parent deletion.
|
|
lv_indev_delete(pointer);
|
|
lv_indev_delete(encoder);
|
|
lv_display_delete(display);
|
|
lv_deinit();
|
|
}
|