Skip to content
Merged
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
12 changes: 11 additions & 1 deletion docs/assembly.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ the invalidated area. The view's root is always treated as opaque.
- `a` can be a variable: changing it redraws the box. A translucent box and
everything in it is never opaque - a redraw starts beneath it, so the
blend never adds up. `OPAQUE` on such a box is ignored.
- `OPACITY 0` draws nothing; the box is still there for input.
- `OPACITY 0` draws nothing, and the box (or one inside it) takes no contact: a touch goes to what is seen beneath it, as with a page's screen hidden behind the one shown.

### Scrolling

Expand Down Expand Up @@ -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:
Expand All @@ -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.

Expand Down
6 changes: 3 additions & 3 deletions docs/binary-format.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.

Expand Down
6 changes: 4 additions & 2 deletions include/dmview_format.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),

Expand Down
16 changes: 16 additions & 0 deletions src/exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ static void scroll_to(struct libdmview* v, uint16_t box, int32_t x, int32_t y)
view_invalidate_deps(v, v->dep_words - 1U, DEP_SCROLL);
}

void view_scroll_to(struct libdmview* v, uint16_t box, int32_t x, int32_t y)
{
scroll_to(v, box, x, y);
}

/* ---- Boxes ---- */

static void enter_box(struct libdmview* v, const uint8_t* insn)
Expand Down Expand Up @@ -258,6 +263,8 @@ static void enter_box(struct libdmview* v, const uint8_t* insn)
f->view_clip = b->clip;
f->box = index;
f->alpha = parent->alpha;
if (v->drawing)
b->seen = (uint8_t)((parent->alpha > 255u) ? 255u : parent->alpha);
}

/* ---- The interpreter ---- */
Expand Down Expand Up @@ -362,6 +369,8 @@ static void run(struct libdmview* v, uint32_t pc, bool box_region)
int32_t a = v16(v, insn, 0, OPOFF(OPACITY, 0));
a = (a < 0) ? 0 : (a > DMV_OPACITY_MAX) ? DMV_OPACITY_MAX : a;
f->alpha = (v->frames[v->depth - 1].alpha * (uint32_t)a + 127u) / 255u;
if (f->box != ROOT)
v->boxes[f->box].seen = (uint8_t)((f->alpha > 255u) ? 255u : f->alpha);
}
break;

Expand Down Expand Up @@ -544,6 +553,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:
Expand Down
60 changes: 57 additions & 3 deletions src/input.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,15 @@ static bool contains(const rect_t* r, int32_t x, int32_t y)

/* The topmost visible box under (x, y) - boxes are drawn in code order, so
* the one starting last is on top - or the nearest parent of it that takes
* the contact. */
* the contact. A box not seen (its opacity, or one around it, 0: a screen
* hidden behind the shown one) takes none - what is seen beneath does. */
static int32_t hit_test(const struct libdmview* v, int32_t x, int32_t y)
{
int32_t hit = ROOT;
for (uint32_t i = 0; i < v->box_count; i++)
{
const rbox_t* b = &v->boxes[i];
if ((b->flags & BOXF_VISIBLE) != 0 && contains(&b->clip, x, y) &&
if ((b->flags & BOXF_VISIBLE) != 0 && b->seen != 0 && contains(&b->clip, x, y) &&
(hit == ROOT || b->begin > v->boxes[hit].begin))
hit = (int32_t)i;
}
Expand All @@ -88,6 +89,33 @@ static int32_t hit_test(const struct libdmview* v, int32_t x, int32_t y)
return hit;
}

/* Whether a box scrolls along an axis: SCROLL, its content larger than it, the axis allowed */
static bool scrolls(const rbox_t* b, bool vertical)
{
if ((b->flags & BOXF_VISIBLE) == 0 || b->seen == 0)
return false;
uint8_t axis = vertical ? DMV_SCROLL_VERTICAL : DMV_SCROLL_HORIZONTAL;
bool allowed = (b->scroll_flags & (DMV_SCROLL_VERTICAL | DMV_SCROLL_HORIZONTAL)) == 0 || (b->scroll_flags & axis) != 0;
return allowed && (vertical ? b->ch > b->bounds.y1 - b->bounds.y0 : b->cw > b->bounds.x1 - b->bounds.x0);
}

/* The scroll box a contact at (x, y) moving along an axis drags: the topmost seen box there, or the nearest box
* around it that scrolls that way */
static int32_t scroll_box_at(const struct libdmview* v, int32_t x, int32_t y, bool vertical)
{
int32_t top = ROOT;
for (uint32_t i = 0; i < v->box_count; i++)
{
const rbox_t* b = &v->boxes[i];
if ((b->flags & BOXF_VISIBLE) != 0 && b->seen != 0 && contains(&b->clip, x, y) &&
(top == ROOT || b->begin > v->boxes[top].begin))
top = (int32_t)i;
}
while (top != ROOT && !scrolls(&v->boxes[top], vertical))
top = v->boxes[top].parent;
return top;
}

static void fire(struct libdmview* v, int32_t box, uint8_t event, int32_t x, int32_t y, int32_t dx, int32_t dy)
{
uint16_t label = (box != ROOT) ? v->boxes[box].handlers[event] : DMV_NONE;
Expand Down Expand Up @@ -128,6 +156,9 @@ dmod_libdmview_api_declaration(1.0, int, _input, ( libdmview_t view, const dmdrv
v->press_ms = now_ms;
v->last_x = x;
v->last_y = y;
v->start_x = x;
v->start_y = y;
v->scrolling = ROOT;
v->captured = hit_test(v, x, y);
pressed_changed(v, v->captured);
fire(v, v->captured, DMV_EVENT_PRESS, x, y, 0, 0);
Expand All @@ -137,12 +168,35 @@ dmod_libdmview_api_declaration(1.0, int, _input, ( libdmview_t view, const dmdrv
int32_t dx = x - v->last_x, dy = y - v->last_y;
v->last_x = x;
v->last_y = y;
fire(v, v->captured, DMV_EVENT_DRAG, x, y, dx, dy);
int32_t mx = x - v->start_x, my = y - v->start_y;
int32_t ax = (mx < 0) ? -mx : mx, ay = (my < 0) ? -my : my;
if (v->scrolling == ROOT && (ax > (int32_t)v->scrollslop || ay > (int32_t)v->scrollslop))
{
/* Past the threshold: a scroll box around the contact takes it over - the box pressed is released, not clicked */
int32_t box = scroll_box_at(v, v->start_x, v->start_y, ay >= ax);
if (box == ROOT)
box = scroll_box_at(v, v->start_x, v->start_y, ay < ax);
if (box != ROOT)
{
int32_t held = v->captured;
v->scrolling = box;
v->scroll_sx = v->boxes[box].sx;
v->scroll_sy = v->boxes[box].sy;
v->captured = ROOT;
pressed_changed(v, held);
fire(v, held, DMV_EVENT_RELEASE, x, y, 0, 0);
}
}
if (v->scrolling != ROOT)
view_scroll_to(v, (uint16_t)v->scrolling, v->scroll_sx - mx, v->scroll_sy - my);
else
fire(v, v->captured, DMV_EVENT_DRAG, x, y, dx, dy);
}
else if (!down && v->down)
{
int32_t box = v->captured;
v->down = false;
v->scrolling = ROOT;
v->captured = ROOT;
pressed_changed(v, box);
if (box != ROOT)
Expand Down
1 change: 1 addition & 0 deletions src/layout.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 9 additions & 0 deletions src/private.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ typedef struct
int32_t sx, sy; /* Scroll offset */
int32_t cw, ch; /* Scrolled content size */
uint16_t handlers[DMV_EVENT_COUNT]; /* Label word offsets, DMV_NONE */
uint8_t seen; /* Its opacity times the boxes' around it, as last drawn: 0 - not seen, takes no contact */
} rbox_t;

/** One level of box nesting while executing. */
Expand Down Expand Up @@ -277,6 +278,10 @@ struct libdmview
bool down;
bool long_fired;
int32_t last_x, last_y;
int32_t start_x, start_y; /* Where the contact went down */
int32_t scrolling; /* The scroll box the contact drags (past .scrollslop), ROOT: none */
int32_t scroll_sx, scroll_sy; /* Its offset when it took the contact over */
uint16_t scrollslop; /* .scrollslop, px */
uint32_t press_ms;
uint32_t buttons;
bool have_now;
Expand Down Expand Up @@ -321,6 +326,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);

Expand Down Expand Up @@ -352,4 +358,7 @@ uint8_t font_scale_for(const char* spec);
void draw_text(const libdmview_surface_t* s, const rect_t* clip, int32_t x, int32_t y, int32_t w, int32_t h,
const char* text, const font_t* font, const paint_t* paint, uint8_t align);

/* exec.c: a scroll box's offset (clamped to its content) */
void view_scroll_to(struct libdmview* v, uint16_t box, int32_t x, int32_t y);

#endif /* LIBDMVIEW_PRIVATE_H */
1 change: 1 addition & 0 deletions src/validate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
30 changes: 30 additions & 0 deletions src/view.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -215,6 +239,10 @@ static int load_tables(struct libdmview* v, const dmv_input_t* in, const uint8_t
v->height = rd16(h + 14);
v->entry = rd16(h + 18);
v->longpress_ms = rd16(h + 20);
v->scrollslop = rd16(h + 22);
if (v->scrollslop == 0)
v->scrollslop = DMV_DEFAULT_SCROLLSLOP;
v->scrolling = ROOT;

if ((v->code = read_block(in, code_at, v->code_size, &status)) == NULL ||
(v->strings = read_strings(in, strings_at, v->string_count, &status)) == NULL)
Expand Down Expand Up @@ -295,6 +323,8 @@ static int load_tables(struct libdmview* v, const dmv_input_t* in, const uint8_t
return -ENOMEM;
}
memset(v->boxes, 0, v->box_count * sizeof(rbox_t));
for (uint32_t i = 0; i < v->box_count; i++)
v->boxes[i].seen = 255u; /* Seen until a draw says otherwise */
for (uint32_t i = 0; i < v->box_count; i++)
{
const uint8_t* p = raw + i * sizeof(dmv_box_t);
Expand Down
27 changes: 27 additions & 0 deletions tests/fixtures/append.dmvs
Original file line number Diff line number Diff line change
@@ -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
37 changes: 37 additions & 0 deletions tests/fixtures/hidden.dmvs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
; Two screens one on another, as a page's views: the one below shown, with
; a button; the one on top hidden by its opacity ($top), with a box taking
; no contact over the button - and a button of its own elsewhere.
.view hidden
.entry draw

.var $top, int, 0
.var $below, int, 0
.var $above, int, 0

draw:
FILL #000000
BOX @screen1, 0, 0, 64, 32
BOX @button, 10, 10, 20, 10 ; 10..29 x 10..19
ON CLICK, press_below
FILL #0000FF
END
END
BOX @screen2, 0, 0, 64, 32
OPACITY $top
BOX @cover, 0, 0, 40, 32 ; over the button, no handler
FILL #FF0000
END
BOX @other, 44, 10, 10, 10
ON CLICK, press_above
FILL #00FF00
END
END
RET

press_below:
ADD $below, 1
RET

press_above:
ADD $above, 1
RET
Loading
Loading