diff --git a/docs/assembly.md b/docs/assembly.md index d3f2a89..7c1111c 100644 --- a/docs/assembly.md +++ b/docs/assembly.md @@ -489,6 +489,7 @@ variable declared with `.var`; built-in variables are read-only. | 0x48 | `CLAMP` | `$d, lo, hi` | d = min(max(d, lo), hi) | | 0x49 | `TOGGLE` | `$d` | d = (d == 0) ? 1 : 0 | | 0x4A | `FORMAT` | `$d, str, n` | Format n into string variable d: `str` with one `%d` or `%x` - optionally with a width, padded with spaces or, after a `0`, with zeros (`%02d`, `%4x`) - and `%%` for `%` | +| 0x4B | `APPEND` | `$d, str` | Append `str` (a literal or a string variable) to string variable d, as much of it as d's size takes - text put together from pieces (format 0.5) | `str` can be a string variable, so a format can be made by `FORMAT` itself - a clock from minutes: @@ -502,6 +503,15 @@ a clock from minutes: FORMAT $clock, $text, $n ; 12:05 ``` +Text from several pieces - a number and a unit, a name and a value - is +`FORMAT` or `SET` followed by `APPEND`s: + +``` + FORMAT $speed, "%d", $kmh ; "68" + APPEND $speed, " " + APPEND $speed, $unit ; "68 km/h" +``` + A variable is "changed" only when its value differs from the previous one - `SET $v, 5` on a `$v` that already holds 5 invalidates nothing. diff --git a/docs/binary-format.md b/docs/binary-format.md index 205d6d1..f0dbda6 100644 --- a/docs/binary-format.md +++ b/docs/binary-format.md @@ -1,7 +1,7 @@ # dmview Binary Format (`.dmv`) -Status: **version 0.4** - follows the [assembly draft](assembly.md). Version -0.2 added gradients, 0.3 the `OPACITY` instruction, 0.4 `ICON`. +Status: **version 0.5** - follows the [assembly draft](assembly.md). Version +0.2 added gradients, 0.3 the `OPACITY` instruction, 0.4 `ICON`, 0.5 `APPEND`. A `.dmv` file is what `libtodmv` produces from `.dmvs` assembly and what `libdmview` executes. Everything is **little-endian**; every table starts at a @@ -57,7 +57,7 @@ start of the file. An empty table has count 0. A version 0.1 header ends at 80, without the gradient tables. `libtodmv` always writes the 96-byte header, but marks a view with the oldest version -that has what it uses - 0.4 with `ICON`, 0.3 with `OPACITY`, 0.2 with gradients, else 0.1: a +that has what it uses - 0.5 with `APPEND`, 0.4 with `ICON`, 0.3 with `OPACITY`, 0.2 with gradients, else 0.1: a reader that knows only 0.1 runs a 0.1 view, it finds every table through the header and never looks at bytes 80 ... 95. diff --git a/include/dmview_format.h b/include/dmview_format.h index 10d2221..fd09951 100644 --- a/include/dmview_format.h +++ b/include/dmview_format.h @@ -25,10 +25,10 @@ /** Format version this header describes. */ #define DMV_VERSION_MAJOR 0 -#define DMV_VERSION_MINOR 4 +#define DMV_VERSION_MINOR 5 /** Header size: version 0.2 added the gradient tables at its end (0.3: - * OPACITY, 0.4: ICON - no change to the header). */ + * OPACITY, 0.4: ICON, 0.5: APPEND - no change to the header). */ #define DMV_HEADER_SIZE 96u #define DMV_HEADER_SIZE_0_1 80u @@ -106,6 +106,7 @@ typedef enum DMV_OP_CLAMP = 0x48, DMV_OP_TOGGLE = 0x49, DMV_OP_FORMAT = 0x4A, + DMV_OP_APPEND = 0x4B, /* Input */ DMV_OP_ON = 0x60, @@ -607,6 +608,7 @@ static const dmv_opcode_info_t dmv_opcode_table[DMV_OPCODE_TABLE_SIZE] = { OP(DMV_OP_CLAMP, "CLAMP", VARIA, 3, 0, F_NONE, false, VAR, V32, V32), OP(DMV_OP_TOGGLE, "TOGGLE", VARIA, 1, 0, F_NONE, false, VAR), OP(DMV_OP_FORMAT, "FORMAT", VARIA, 3, 0, F_NONE, false, VAR, STR, V32), + OP(DMV_OP_APPEND, "APPEND", VARIA, 2, 0, F_NONE, false, VAR, STR), OP(DMV_OP_ON, "ON", INPUT, 2, 0, F_NONE, false, EVENT, LABEL), diff --git a/src/exec.c b/src/exec.c index 8408e91..f158a1d 100644 --- a/src/exec.c +++ b/src/exec.c @@ -544,6 +544,13 @@ static void run(struct libdmview* v, uint32_t pc, bool box_region) view_set_string(v, rd16(insn + OPOFF(FORMAT, 0)), text); break; } + case DMV_OP_APPEND: + { + uint16_t d = rd16(insn + OPOFF(APPEND, 0)); + record(v, d / 32U, 1U << (d % 32U)); + view_append_string(v, d, vstr(v, insn, 1, OPOFF(APPEND, 1))); + break; + } /* ---- Input ---- */ case DMV_OP_ON: diff --git a/src/layout.h b/src/layout.h index f288500..eddfc33 100644 --- a/src/layout.h +++ b/src/layout.h @@ -47,6 +47,7 @@ static inline uint8_t layout_offset(uint8_t op, unsigned i) case DMV_OP_CLAMP: { static const uint8_t o[] = { 4, 8, 12 }; return o[i]; } case DMV_OP_TOGGLE: { static const uint8_t o[] = { 4 }; return o[i]; } case DMV_OP_FORMAT: { static const uint8_t o[] = { 4, 6, 8 }; return o[i]; } + case DMV_OP_APPEND: { static const uint8_t o[] = { 4, 6 }; return o[i]; } case DMV_OP_ON: { static const uint8_t o[] = { 4, 6 }; return o[i]; } case DMV_OP_REDRAW: case DMV_OP_EXEC: diff --git a/src/private.h b/src/private.h index 76b5a9f..be603aa 100644 --- a/src/private.h +++ b/src/private.h @@ -321,6 +321,7 @@ void draw_icon(const libdmview_surface_t* s, const rect_t* clip, int3 const char* view_string(const struct libdmview* v, uint32_t index); void view_set_int(struct libdmview* v, uint32_t index, int32_t value); void view_set_string(struct libdmview* v, uint32_t index, const char* value); +void view_append_string(struct libdmview* v, uint32_t index, const char* tail); void view_invalidate_deps(struct libdmview* v, uint32_t word, uint32_t bit); void view_mark_dirty(struct libdmview* v, int32_t box); diff --git a/src/validate.c b/src/validate.c index e985506..1665416 100644 --- a/src/validate.c +++ b/src/validate.c @@ -274,6 +274,7 @@ static int dest_type(uint8_t opcode) { case DMV_OP_SET: return -1; /* Either */ case DMV_OP_FORMAT: return DMV_VAR_STR; + case DMV_OP_APPEND: return DMV_VAR_STR; default: return DMV_VAR_INT; } } diff --git a/src/view.c b/src/view.c index 147c8ca..6694a6f 100644 --- a/src/view.c +++ b/src/view.c @@ -162,6 +162,30 @@ void view_set_string(struct libdmview* v, uint32_t index, const char* value) (void)Dmod_SetEnv(view_string(v, v->vars[index].env), dst, 1); } +/* The string after what the variable holds - as much of it as its size takes */ +void view_append_string(struct libdmview* v, uint32_t index, const char* tail) +{ + char* dst = v->strs[index]; + size_t n = strlen(dst), room = v->vars[index].capacity; + size_t m = strlen(tail); + if (n >= room || m == 0) + return; + if (m > room - n) + m = room - n; + if (tail >= dst && tail <= dst + room) + { + /* Appended to itself: copy it from before it grows */ + memmove(dst + n, tail, m); + } + else + memcpy(dst + n, tail, m); + dst[n + m] = '\0'; + if (!v->drawing) + view_invalidate_deps(v, index / 32U, 1U << (index % 32U)); + if ((v->vars[index].flags & DMV_VARF_ENV) != 0) + (void)Dmod_SetEnv(view_string(v, v->vars[index].env), dst, 1); +} + /* ---- Loading ---- */ static void free_view(struct libdmview* v) diff --git a/tests/fixtures/append.dmvs b/tests/fixtures/append.dmvs new file mode 100644 index 0000000..d688276 --- /dev/null +++ b/tests/fixtures/append.dmvs @@ -0,0 +1,27 @@ +; APPEND: text put together from pieces - every line is drawn above the same +; text as a literal, in the built-in font, on a 64x32 surface +.view append +.size 64, 32 +.entry draw + +.font fixed, "builtin-8" + +.var $a, str[12], "" +.var $b, str[4], "" +.var $t, str[8], "km" +.var $n, str[8], "" + +draw: + FILL #000000 + FORMAT $n, "%d", 68 + SET $a, $n + APPEND $a, " " + APPEND $a, $t + APPEND $a, "/h" + TEXT 0, 0, 64, 8, $a, fixed, #FFFFFF, LEFT ; "68 km/h" + TEXT 0, 8, 64, 8, "68 km/h", fixed, #FFFFFF, LEFT + SET $b, "ab" + APPEND $b, "cdef" + TEXT 0, 16, 64, 8, $b, fixed, #FFFFFF, LEFT ; "abcd": its size + TEXT 0, 24, 64, 8, "abcd", fixed, #FFFFFF, LEFT + RET diff --git a/tests/libdmview_test.c b/tests/libdmview_test.c index 9e0e9ba..d61c98d 100644 --- a/tests/libdmview_test.c +++ b/tests/libdmview_test.c @@ -389,6 +389,16 @@ DMOD_TEST_STEP(libdmview_formats_with_padding) DMOD_TEST_EXPECT_EQ(libdmview_get_size(NULL, &w, &h), -EINVAL); } +DMOD_TEST_STEP(libdmview_appends_strings) +{ + DMOD_TEST_EXPECT_TRUE(open_fixture(FIXTURE("append.dmv"))); + if (g_view == NULL) + return; + DMOD_TEST_EXPECT_EQ(libdmview_render(g_view, &g_s32, NULL), 1); + DMOD_TEST_EXPECT_TRUE(same_as_below(0, 64, 0)); /* "68" " " "km" "/h": "68 km/h" */ + DMOD_TEST_EXPECT_TRUE(same_as_below(0, 64, 16)); /* "ab" + "cdef" into str[4]: "abcd" */ +} + /* ---- Antialiasing ---- */