diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a584ee..b6b1e95 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,5 +38,7 @@ jobs: - name: Run tests with dmod_loader run: | export DMOD_DMF_DIR=$(pwd)/build/dmf + # The test's own dependencies, and dmvs_html's (dmvs_js: the test does not call it, the module does) dmf-get install -d ${DMOD_DMF_DIR}/test_dmvs_html-local.dmd -y + dmf-get install -d ${DMOD_DMF_DIR}/dmvs_html-local.dmd -y dmod_loader build/dmf/test_dmvs_html.dmf diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c47ad7..5cbbab7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,10 +71,13 @@ dmod_add_library(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION} src/paint.c src/svg.c src/script.c + src/build.c + src/anim.c ) dmod_link_modules(${DMOD_MODULE_NAME} - dmvsi@>=0.2 + dmvsi@>=0.5 + dmvs_js@>=0.1 ) target_include_directories(${DMOD_MODULE_NAME} PRIVATE diff --git a/Makefile b/Makefile index 519e4ca..08e3d19 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ DMOD_MODULE_VERSION=0.1 DMOD_AUTHOR_NAME=Patryk Kubiak # The list of C sources -DMOD_CSOURCES=src/dmvs_html.c src/html.c src/css.c src/tailwind.c src/style.c src/layout.c src/paint.c src/svg.c src/script.c +DMOD_CSOURCES=src/dmvs_html.c src/html.c src/css.c src/tailwind.c src/style.c src/layout.c src/paint.c src/svg.c src/script.c src/build.c src/anim.c # The list of C++ sources DMOD_CXXSOURCES= diff --git a/README.md b/README.md index 3e9ac91..ab3e939 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,9 @@ makes a dmview view of it. It has its own HTML parser, CSS cascade and layout - no browser, no dependencies - so it runs on a PC at build time and on a device. It is made for **UI pages**: the screens a designer (or an AI) draws in HTML with -Tailwind CSS - and the little JavaScript that switches between them, which -it works out at conversion and makes the view's variables and handlers. +Tailwind CSS - and their JavaScript, which +[dmvs_js](https://github.com/choco-technologies/dmvs_js) compiles into the +view's variables, handlers and timers. ## What it does @@ -70,52 +71,75 @@ CSS does; text is measured with them as the view will draw it. Icon fonts ## Scripts -A script is not run on the device: what its click handlers do is worked -out at conversion. Understood is what switches screens and toggles things: +A script isn't run on the device. The page's `")); + dmvsi_doc_t doc = convert(TEST_FILE("texts.html"), "screen", 0, 0, &status); + DMOD_TEST_EXPECT_EQ(status, 0); + if (doc == NULL) + return; + const dmvsi_node_t* root = dmvsi_root(doc); + + /* #speed: empty at first - a text of its variable, centred in its box (the scripts' characters: in its font) */ + dmvsi_var_t speed = var_named(doc, "speed_text"); + const dmvsi_node_t* st = text_of_var(root, speed); + DMOD_TEST_EXPECT_TRUE(speed != 0 && st != NULL); + if (st != NULL) + { + DMOD_TEST_EXPECT_TRUE(st->u.text.align == DMVSI_TEXT_CENTER && st->u.text.width == DMVSI_PX(100)); + } + /* #temp: its text and its number (21.5) - the click sets both */ + dmvsi_var_t temp = var_named(doc, "temp_text"); + dmvsi_var_t number = var_named(doc, "temp_number"); + DMOD_TEST_EXPECT_TRUE(text_of_var(root, temp) != NULL && var_initial(doc, number) == 21500); + dmvsi_var_info_t info; + DMOD_TEST_EXPECT_TRUE(dmvsi_var_info(doc, temp, &info) == 0 && strcmp(info.text, "21.5\xC2\xB0" "C") == 0); + const dmvsi_node_t* up = group_named(root->first, "up"); + DMOD_TEST_EXPECT_TRUE(up != NULL && up->click != 0); + if (up != NULL) + { + const dmvsi_action_t* a = NULL; + uint32_t n = flat_actions(doc, up->click, &a); + bool sets_text = false, sets_number = false; + for (uint32_t i = 0; i < n; i++) + { + sets_text = sets_text || (a[i].kind == DMVSI_ACT_SET && a[i].var == temp); + sets_number = sets_number || (a[i].kind == DMVSI_ACT_SET && a[i].var == number); + } + DMOD_TEST_EXPECT_TRUE(sets_text && sets_number); + } + /* The interval: a timer of the view */ + uint16_t ms = 0; + dmvsi_handler_t h = 0; + DMOD_TEST_EXPECT_TRUE(dmvsi_timer_at(doc, 0, &ms, &h) && ms >= 10 && ms <= 50 && h != 0); + dmvsi_free(doc); +} + +static uint32_t count_texts(const dmvsi_node_t* n, const char* text) +{ + uint32_t count = 0; + for (; n != NULL; n = n->next) + { + if (n->kind == DMVSI_NODE_TEXT && n->u.text.var == 0 && strcmp(n->u.text.text, text) == 0) + count++; + if (n->kind == DMVSI_NODE_GROUP) + count += count_texts(n->first, text); + } + return count; +} + +/* The groups clicked whose handler sets a variable */ +static uint32_t clicks_setting(dmvsi_doc_t doc, const dmvsi_node_t* n, dmvsi_var_t var) +{ + uint32_t count = 0; + for (; n != NULL; n = n->next) + { + if (n->kind != DMVSI_NODE_GROUP) + continue; + if (n->click != 0) + { + const dmvsi_action_t* a = NULL; + uint32_t k = flat_actions(doc, n->click, &a); + bool sets = false; + for (uint32_t i = 0; i < k && !sets; i++) + sets = a[i].kind == DMVSI_ACT_SET && a[i].var == var; + count += sets ? 1U : 0U; + } + count += clicks_setting(doc, n->first, var); + } + return count; +} + +DMOD_TEST_STEP(dmvs_html_converts_what_scripts_build) +{ + int status = -1; + DMOD_TEST_EXPECT_TRUE(write_file(TEST_FILE("build.html"), + "
-
" + "
")); + dmvsi_doc_t doc = convert(TEST_FILE("build.html"), "screen", 0, 0, &status); + DMOD_TEST_EXPECT_EQ(status, 0); + if (doc == NULL) + return; + const dmvsi_node_t* root = dmvsi_root(doc); + + /* The rows as the page loads: their texts, the current one's background, each clicked */ + DMOD_TEST_EXPECT_TRUE(find(root->first, DMVSI_NODE_TEXT, "One", NULL) != NULL); + DMOD_TEST_EXPECT_TRUE(find(root->first, DMVSI_NODE_TEXT, "2:10", NULL) != NULL); + DMOD_TEST_EXPECT_TRUE(find(root->first, DMVSI_NODE_TEXT, "0:07", NULL) != NULL); + dmvsi_rect_t on = { 0, DMVSI_PX(40), DMVSI_PX(200), DMVSI_PX(20) }; + const dmvsi_node_t* fill = find(root->first, DMVSI_NODE_RECT, NULL, &on); + DMOD_TEST_EXPECT_TRUE(fill != NULL && fill->u.fill.paint.color == 0xFF3366FFu); + dmvsi_var_t title = var_named(doc, "title_text"); + uint32_t clicked = clicks_setting(doc, root->first, title); + DMOD_TEST_EXPECT_EQ(clicked, 3u); + /* Rendered anew on a click: each row's class list one of two - its look's variable set */ + dmvsi_var_t look = var_named(doc, "div_look"); + DMOD_TEST_EXPECT_TRUE(look != 0 && clicks_setting(doc, root->first, look) == 3u); + /* The list is in a screen hidden as the page loads: a row's other look is seen when it is shown too */ + DMOD_TEST_EXPECT_TRUE(count_texts(root->first, "Two") >= 2u); + dmvsi_free(doc); +} + +DMOD_TEST_STEP(dmvs_html_converts_css_animations) +{ + int status = -1; + DMOD_TEST_EXPECT_TRUE(write_file(TEST_FILE("anim.html"), + "
")); + dmvsi_doc_t doc = convert(TEST_FILE("anim.html"), "screen", 0, 0, &status); + DMOD_TEST_EXPECT_EQ(status, 0); + if (doc == NULL) + return; + /* Its y a variable, 4 px up as it starts; a timer moving it there and back, 400 ms each way */ + const dmvsi_node_t* bar = group_named(dmvsi_root(doc)->first, "bar"); + DMOD_TEST_EXPECT_TRUE(bar != NULL && bar->bind[DMVSI_BIND_Y] != 0); + if (bar != NULL) + DMOD_TEST_EXPECT_EQ(var_initial(doc, bar->bind[DMVSI_BIND_Y]), DMVSI_PX(6)); + uint16_t ms = 0; + dmvsi_handler_t h = 0; + DMOD_TEST_EXPECT_TRUE(dmvsi_timer_at(doc, 0, &ms, &h) && ms == 20u); + const dmvsi_action_t* a = NULL; + uint32_t n = (h != 0) ? dmvsi_handler_actions(doc, h, &a) : 0; + uint32_t animates = 0, down = 0; + for (uint32_t i = 0; i < n; i++) + { + if (a[i].kind != DMVSI_ACT_ANIMATE || bar == NULL || a[i].var != bar->bind[DMVSI_BIND_Y]) + continue; + animates++; + down += (a[i].value == DMVSI_PX(10) && a[i].duration == 400 && a[i].easing[0] == 420) ? 1U : 0U; + } + DMOD_TEST_EXPECT_EQ(animates, 4u); /* 0 -> 50 -> 100, and back */ + DMOD_TEST_EXPECT_TRUE(down >= 1u); + dmvsi_free(doc); +}