~hp/moony.lv2

a817c3d6aad48f83d734445ca21295069bb7f0a3 — Hanspeter Portner 11 months ago 23359a1
Add missing widgets to d2tk ui
2 files changed, 392 insertions(+), 289 deletions(-)

M meson_options.txt
M plugin/d2tk_ui.c
M meson_options.txt => meson_options.txt +1 -1
@@ 43,4 43,4 @@ option('lv2libdir',
	type : 'string',
	value : 'lib/lv2')

option('version', type : 'string', value : '0.41.95')
option('version', type : 'string', value : '0.41.97')

M plugin/d2tk_ui.c => plugin/d2tk_ui.c +391 -288
@@ 417,6 417,13 @@ _dynparams_get(plughandle_t *handle, LV2_URID prop)
static dynparam_t *
_dynparams_add(plughandle_t *handle, LV2_URID prop)
{
	// ignore canvas graph and aspectRatio properties
	if( (prop == handle->canvas.urid.Canvas_graph)
	||	(prop == handle->canvas.urid.Canvas_aspectRatio) )
	{
			return NULL;
	}

	const size_t new_sz = sizeof(dynparam_t) * ++handle->ndynparams;

	handle->dynparams = realloc(handle->dynparams, new_sz); //TODO check


@@ 450,6 457,7 @@ _dynparams_clr(plughandle_t *handle)
	}

	handle->ndynparams = 0;
	handle->graph_size = 0;
}

static void


@@ 631,6 639,14 @@ _dyn_prop_rem(plughandle_t *handle, LV2_URID subj, LV2_URID prop,
}

static void
_dyn_prop_set_body(dynparam_t *dynparam, uint32_t size, const void *body)
{
	dynparam->size = size;
	dynparam->val = realloc(dynparam->val, size); //FIXME check
	memcpy(dynparam->val, body, size);
}

static void
_dyn_prop_set(plughandle_t *handle, LV2_URID subj, LV2_URID prop,
	const LV2_Atom *atom)
{


@@ 646,9 662,7 @@ _dyn_prop_set(plughandle_t *handle, LV2_URID subj, LV2_URID prop,
		return; //TODO log
	}

	dynparam->size = atom->size;
	dynparam->val = realloc(dynparam->val, atom->size); //FIXME check
	memcpy(dynparam->val, LV2_ATOM_BODY_CONST(atom), atom->size);
	_dyn_prop_set_body(dynparam, atom->size, LV2_ATOM_BODY_CONST(atom));
}

static void


@@ 1058,32 1072,19 @@ _expose_editor(plughandle_t *handle, const d2tk_rect_t *rect)
static void
_aspect_correction(d2tk_rect_t *rect, float aspect_ratio)
{
	const d2tk_coord_t lon = rect->w >= rect->h
		? rect->w
		: rect->h;
	const d2tk_coord_t sho = rect->w < rect->h
		? rect->w
		: rect->h;
	const float rect_ar = (float)rect->w / rect->h;

	if(aspect_ratio <= 0.f)
	{
		aspect_ratio = 1.f;
	}

	if(aspect_ratio < 1.f)
	{
		rect->w = sho * aspect_ratio;
		rect->h = sho;
		// fill whole are
	}
	else if(aspect_ratio == 1.f)
	else if(rect_ar < aspect_ratio) // keep width
	{
		rect->w = sho;
		rect->h = sho;
		rect->h = rect->w / aspect_ratio;
	}
	else //if(aspect_ratio > 1.f)
	else if (rect_ar > aspect_ratio) // keep height
	{
		rect->w = lon;
		rect->h = lon / aspect_ratio;
		rect->w = rect->h * aspect_ratio;
	}
}



@@ 1108,11 1109,6 @@ _expose_canvas_graph(plughandle_t *handle, const d2tk_rect_t *_rect)
{
	d2tk_base_t *base = d2tk_frontend_get_base(handle->dpugl);

	if(handle->graph_size == 0)
	{
		return;
	}

	d2tk_rect_t rect = *_rect;
	_aspect_correction(&rect, handle->state.aspect_ratio);



@@ 1197,29 1193,6 @@ _expose_canvas_graph(plughandle_t *handle, const d2tk_rect_t *_rect)
}

static void
_expose_graph_minimize(plughandle_t *handle, const d2tk_rect_t *rect)
{
	d2tk_base_t *base = d2tk_frontend_get_base(handle->dpugl);

	static const char *path [2] = { "eye-off.png", "eye.png" };
	static const char tip [2][10] = { "show graph", "hide graph" };

	bool visible = !handle->state.graph_hidden;
	const d2tk_state_t state = d2tk_base_toggle_label_image(base, D2TK_ID,
		0, NULL, D2TK_ALIGN_CENTERED, -1, path[visible], rect, &visible);

	if(d2tk_state_is_changed(state))
	{
		handle->state.graph_hidden = !handle->state.graph_hidden;
		_message_set_key(handle, handle->urid_graphHidden);
	}
	if(d2tk_state_is_over(state))
	{
		d2tk_base_set_tooltip(base, sizeof(tip[visible]), tip[visible], handle->tip_height);
	}
}

static void
_expose_manual(plughandle_t *handle, const d2tk_rect_t *rect)
{
	d2tk_frontend_t *dpugl = handle->dpugl;


@@ 1261,14 1234,13 @@ _expose_manual(plughandle_t *handle, const d2tk_rect_t *rect)
}

static void
_expose_graph_footer(plughandle_t *handle, const d2tk_rect_t *rect)
_expose_left(plughandle_t *handle, const d2tk_rect_t *rect)
{
	d2tk_base_t *base = d2tk_frontend_get_base(handle->dpugl);

	const d2tk_coord_t frac [3] = {
		0, rect->h, rect->h
	};
	D2TK_BASE_LAYOUT(rect, 3, frac, D2TK_FLAG_LAYOUT_X_ABS, lay)
	const d2tk_coord_t frac [3] = { 0, 5, 0 };
	const size_t n = handle->graph_size ? 3 : 1;
	D2TK_BASE_LAYOUT(rect, n, frac, D2TK_FLAG_LAYOUT_Y_ABS, lay)
	{
		const unsigned k = d2tk_layout_get_index(lay);
		const d2tk_rect_t *lrect = d2tk_layout_get_rect(lay);


@@ 1277,50 1249,15 @@ _expose_graph_footer(plughandle_t *handle, const d2tk_rect_t *rect)
		{
			case 0:
			{
				static char lbl [] = "canvas•graph";

				d2tk_base_label(base, sizeof(lbl), lbl, 0.5f, lrect,
					D2TK_ALIGN_LEFT | D2TK_ALIGN_MIDDLE);
				_expose_editor(handle, lrect);
			} break;
			case 1:
			{
				_expose_manual(handle, lrect);
				d2tk_base_separator(base, lrect, D2TK_FLAG_SEPARATOR_Y);
			} break;
			case 2:
			{
				_expose_graph_minimize(handle, lrect);
			} break;
		}
	}
}

static void
_expose_left(plughandle_t *handle, const d2tk_rect_t *rect)
{
	const d2tk_coord_t frac [2] = {
		handle->state.graph_hidden ? 1 : 0,
		handle->state.editor_hidden ? 1 : 0
	};
	D2TK_BASE_LAYOUT(rect, 2, frac, D2TK_FLAG_LAYOUT_Y_ABS, lay)
	{
		const unsigned k = d2tk_layout_get_index(lay);
		const d2tk_rect_t *lrect = d2tk_layout_get_rect(lay);

		switch(k)
		{
			case 0:
			{
				if(!handle->state.graph_hidden)
				{
					_expose_canvas_graph(handle, lrect);
				}
			} break;
			case 1:
			{
				if(!handle->state.editor_hidden)
				{
					_expose_editor(handle, lrect);
				}
				_expose_canvas_graph(handle, lrect);
			} break;
		}
	}


@@ 1367,7 1304,7 @@ _body_as_double(plughandle_t *handle, const void *data, LV2_URID range)

static void
_expose_slot_enum(plughandle_t *handle, dynparam_t *dynparam,
	const d2tk_rect_t *rect, size_t lbl_len, const char *lbl, unsigned k)
	const d2tk_rect_t *rect, unsigned k)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);


@@ 1377,8 1314,6 @@ _expose_slot_enum(plughandle_t *handle, dynparam_t *dynparam,
		return;
	}

	d2tk_base_label(base, lbl_len, lbl, 0.5f, rect, D2TK_ALIGN_CENTERED);

	// derive item number
	size_t nitms = 0;
	LV2_ATOM_TUPLE_FOREACH(dynparam->points, itm)


@@ 1417,8 1352,20 @@ _expose_slot_enum(plughandle_t *handle, dynparam_t *dynparam,
		itms[nitms++] = LV2_ATOM_BODY_CONST(label);
	}

	const d2tk_state_t state = d2tk_base_combo(base, D2TK_ID_IDX(k),
		nitms, itms, rect, &idx);
	d2tk_rect_t bnd;
	d2tk_rect_shrink_x(&bnd, rect, rect->h/2);

	d2tk_state_t state;
	if(dynparam->writable)
	{
		state = d2tk_base_combo(base, D2TK_ID_IDX(k),
			nitms, itms, &bnd, &idx);
	}
	else
	{
		state = d2tk_base_label(base, -1, itms[idx], 0.5f, &bnd,
			D2TK_ALIGN_LEFT | D2TK_ALIGN_MIDDLE);
	}

	if(d2tk_state_is_changed(state))
	{


@@ 1446,15 1393,11 @@ _expose_slot_enum(plughandle_t *handle, dynparam_t *dynparam,

		_message_set_dynparam(handle, dynparam);
	}
	if(d2tk_state_is_over(state) && dynparam->comment)
	{
		d2tk_base_set_tooltip(base, -1, dynparam->comment, handle->tip_height);
	}
}

static void
_expose_slot_bool(plughandle_t *handle, dynparam_t *dynparam,
	const d2tk_rect_t *rect, size_t lbl_len, const char *lbl, unsigned k)
	const d2tk_rect_t *rect, unsigned k)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);


@@ 1467,22 1410,18 @@ _expose_slot_bool(plughandle_t *handle, dynparam_t *dynparam,
	bool val = *(int32_t *)dynparam->val;

	const d2tk_state_t state = d2tk_base_spinner_bool(base,
		D2TK_ID_IDX(k), rect, lbl_len, lbl, &val, _dynparam_flag(dynparam));
		D2TK_ID_IDX(k), rect, 0, NULL, &val, _dynparam_flag(dynparam));

	if(d2tk_state_is_changed(state))
	{
		*(int32_t *)dynparam->val = val;
		_message_set_dynparam(handle, dynparam);
	}
	if(d2tk_state_is_over(state) && dynparam->comment)
	{
		d2tk_base_set_tooltip(base, -1, dynparam->comment, handle->tip_height);
	}
}

static void
_expose_slot_int(plughandle_t *handle, dynparam_t *dynparam,
	const d2tk_rect_t *rect, size_t lbl_len, const char *lbl, unsigned k)
	const d2tk_rect_t *rect, unsigned k)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);


@@ 1497,21 1436,17 @@ _expose_slot_int(plughandle_t *handle, dynparam_t *dynparam,
	int32_t *val = dynparam->val;

	const d2tk_state_t state = d2tk_base_spinner_int32(base,
		D2TK_ID_IDX(k), rect, lbl_len, lbl, min, val, max, _dynparam_flag(dynparam));
		D2TK_ID_IDX(k), rect, 0, NULL, min, val, max, _dynparam_flag(dynparam));

	if(d2tk_state_is_changed(state))
	{
		_message_set_dynparam(handle, dynparam);
	}
	if(d2tk_state_is_over(state) && dynparam->comment)
	{
		d2tk_base_set_tooltip(base, -1, dynparam->comment, handle->tip_height);
	}
}

static void
_expose_slot_long(plughandle_t *handle, dynparam_t *dynparam,
	const d2tk_rect_t *rect, size_t lbl_len, const char *lbl, unsigned k)
	const d2tk_rect_t *rect, unsigned k)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);


@@ 1526,21 1461,17 @@ _expose_slot_long(plughandle_t *handle, dynparam_t *dynparam,
	int64_t *val = dynparam->val;

	const d2tk_state_t state = d2tk_base_spinner_int64(base,
		D2TK_ID_IDX(k), rect, lbl_len, lbl, min, val, max, _dynparam_flag(dynparam));
		D2TK_ID_IDX(k), rect, 0, NULL, min, val, max, _dynparam_flag(dynparam));

	if(d2tk_state_is_changed(state))
	{
		_message_set_dynparam(handle, dynparam);
	}
	if(d2tk_state_is_over(state) && dynparam->comment)
	{
		d2tk_base_set_tooltip(base, -1, dynparam->comment, handle->tip_height);
	}
}

static void
_expose_slot_float(plughandle_t *handle, dynparam_t *dynparam,
	const d2tk_rect_t *rect, size_t lbl_len, const char *lbl, unsigned k)
	const d2tk_rect_t *rect, unsigned k)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);


@@ 1555,21 1486,17 @@ _expose_slot_float(plughandle_t *handle, dynparam_t *dynparam,
	float *val = dynparam->val;

	const d2tk_state_t state = d2tk_base_spinner_float(base,
		D2TK_ID_IDX(k), rect, lbl_len, lbl, min, val, max, _dynparam_flag(dynparam));
		D2TK_ID_IDX(k), rect, 0, NULL, min, val, max, _dynparam_flag(dynparam));

	if(d2tk_state_is_changed(state))
	{
		_message_set_dynparam(handle, dynparam);
	}
	if(d2tk_state_is_over(state) && dynparam->comment)
	{
		d2tk_base_set_tooltip(base, -1, dynparam->comment, handle->tip_height);
	}
}

static void
_expose_slot_double(plughandle_t *handle, dynparam_t *dynparam,
	const d2tk_rect_t *rect, size_t lbl_len, const char *lbl, unsigned k)
	const d2tk_rect_t *rect, unsigned k)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);


@@ 1584,21 1511,23 @@ _expose_slot_double(plughandle_t *handle, dynparam_t *dynparam,
	double *val = dynparam->val;

	const d2tk_state_t state = d2tk_base_spinner_double(base,
		D2TK_ID_IDX(k), rect, lbl_len, lbl, min, val, max, _dynparam_flag(dynparam));
		D2TK_ID_IDX(k), rect, 0, NULL, min, val, max, _dynparam_flag(dynparam));

	if(d2tk_state_is_changed(state))
	{
		_message_set_dynparam(handle, dynparam);
	}
	if(d2tk_state_is_over(state) && dynparam->comment)
	{
		d2tk_base_set_tooltip(base, -1, dynparam->comment, handle->tip_height);
	}
}

static const d2tk_lineedit_filter_t urid_filter = {
	.completion_cb = NULL,
	.hints_cb = NULL,
	.free_hints_cb = NULL
};

static void
_expose_slot_urid(plughandle_t *handle, dynparam_t *dynparam,
	const d2tk_rect_t *rect, size_t lbl_len, const char *lbl, unsigned k)
	const d2tk_rect_t *rect, unsigned k)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);


@@ 1608,167 1537,412 @@ _expose_slot_urid(plughandle_t *handle, dynparam_t *dynparam,
		return;
	}

	uint32_t *val = dynparam->val;
//FIXME
#if 0
	char uri [PATH_MAX];
	char txt [2048];
	LV2_URID *val = dynparam->val;

	snprintf(uri, sizeof(uri), "%s",
	snprintf(txt, sizeof(txt), "%s",
		handle->unmap->unmap(handle->unmap->handle, *val));

	//FIXME
	const d2tk_state_t state = d2tk_base_text_field(base, D2TK_ID_IDX(k), rect,
		sizeof(uri), uri, D2TK_ALIGN_MIDDLE | D2TK_ALIGN_LEFT, NULL);
#else
	const char *uri = handle->unmap->unmap(handle->unmap->handle, *val);
	d2tk_rect_t bnd;
	d2tk_rect_shrink_x(&bnd, rect, rect->h/2);

	const d2tk_state_t state = d2tk_base_label(base, -1, uri, 0.5f, rect,
		D2TK_ALIGN_MIDDLE | D2TK_ALIGN_LEFT);
#endif
	d2tk_state_t state;
	if(dynparam->writable)
	{
		state = d2tk_base_lineedit(base, D2TK_ID_IDX(k),
			sizeof(txt), txt, &urid_filter, &bnd, D2TK_FLAG_NONE);
	}
	else
	{
		state = d2tk_base_label(base, sizeof(txt), txt, 0.5f, &bnd,
			D2TK_ALIGN_LEFT | D2TK_ALIGN_MIDDLE);
	}

	if(d2tk_state_is_changed(state))
	{
		*val = handle->map->map(handle->map->handle, uri);
		*val = handle->map->map(handle->map->handle, txt);
		_message_set_dynparam(handle, dynparam);
	}
	if(d2tk_state_is_over(state) && dynparam->comment)
}

static void
_expose_dynparam_clear(plughandle_t *handle, dynparam_t *dynparam,
	const d2tk_rect_t *rect, unsigned k)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);

	static const char path [] = "delete.png";
	static const char none [] = "";
	static const char tip [] = "clear";
	const uint32_t none_sz = dynparam->range == handle->forge.String ? 1 : 0;

	const d2tk_state_t state = d2tk_base_button_image(base, D2TK_ID_IDX(k),
		sizeof(path), path, rect);

	if(d2tk_state_is_changed(state))
	{
		d2tk_base_set_tooltip(base, -1, dynparam->comment, handle->tip_height);
		_dyn_prop_set_body(dynparam, none_sz, none);
		_message_set_dynparam(handle, dynparam);
	}
	if(d2tk_state_is_over(state))
	{
		d2tk_base_set_tooltip(base, sizeof(tip), tip, handle->tip_height);
	}
}

static void
_expose_slot_string(plughandle_t *handle, dynparam_t *dynparam,
	const d2tk_rect_t *rect, size_t lbl_len, const char *lbl, unsigned k)
_expose_dynparam_copy(plughandle_t *handle, dynparam_t *dynparam,
	const char *mime, const d2tk_rect_t *rect, unsigned k)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);

	if(!dynparam->val)
	static const char path [] = "copy.png";
	static const char tip [] = "copy to clipboard";

	const d2tk_state_t state = d2tk_base_button_image(base, D2TK_ID_IDX(k),
		sizeof(path), path, rect);

	if(d2tk_state_is_changed(state))
	{
		return;
		d2tk_frontend_set_clipboard(dpugl, mime, dynparam->val, dynparam->size);
	}
	if(d2tk_state_is_over(state))
	{
		d2tk_base_set_tooltip(base, sizeof(tip), tip, handle->tip_height);
	}
}

static void
_expose_dynparam_paste(plughandle_t *handle, dynparam_t *dynparam,
	const char *mime_in, const d2tk_rect_t *rect, unsigned k)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);

	const char *val = dynparam->val;
	static const char path [] = "clipboard.png";
	static const char tip [] = "paste from clipboard";

	//FIXME
	const d2tk_state_t state = d2tk_base_label(base, -1, val, 0.5f, rect,
		D2TK_ALIGN_MIDDLE | D2TK_ALIGN_LEFT);
	const d2tk_state_t state = d2tk_base_button_image(base, D2TK_ID_IDX(k),
		sizeof(path), path, rect);

	if(d2tk_state_is_changed(state))
	{
		_message_set_dynparam(handle, dynparam);
		size_t txt_len = 0;
		const char *mime_out = mime_in;
		const char *txt = d2tk_frontend_get_clipboard(dpugl, &mime_out, &txt_len);

		if(txt && txt_len && mime_out && !strcmp(mime_in, mime_out))
		{
			_dyn_prop_set_body(dynparam, txt_len, txt);
			_message_set_dynparam(handle, dynparam);
		}
		else
		{
			lv2_log_error(&handle->logger, "[%s] failed to paste text: %s", __func__,
				mime_out);
		}
	}
	if(d2tk_state_is_over(state) && dynparam->comment)
	if(d2tk_state_is_over(state))
	{
		d2tk_base_set_tooltip(base, -1, dynparam->comment, handle->tip_height);
		d2tk_base_set_tooltip(base, sizeof(tip), tip, handle->tip_height);
	}
}

#ifdef _LV2_HAS_REQUEST_VALUE
static void
_expose_slot_chunk(plughandle_t *handle, dynparam_t *dynparam,
	const d2tk_rect_t *rect, size_t lbl_len, const char *lbl, unsigned k)
_expose_blob_load(plughandle_t *handle, LV2_URID prop, LV2_URID range,
	const d2tk_rect_t *rect, unsigned k)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);

	if(!dynparam->val)
	static const char path [] = "save.png";
	static const char tip [] = "load from file";

	if(!handle->request_code)
	{
		return;
	}

	char sz [128];
	const size_t sz_len = snprintf(sz, sizeof(sz), "%s (%"PRIu32" bytes)",
		dynparam->label, dynparam->size);

	//FIXME
	const d2tk_state_t state = d2tk_base_label(base, sz_len, sz, 0.5f, rect,
		D2TK_ALIGN_MIDDLE | D2TK_ALIGN_LEFT);
	const d2tk_state_t state = d2tk_base_button_image(base, D2TK_ID_IDX(k),
		sizeof(path), path, rect);

	if(d2tk_state_is_changed(state))
	{
		_message_set_dynparam(handle, dynparam);
		const LV2UI_Request_Value_Status status = handle->request_code->request(
			handle->request_code->handle, prop, range, NULL);

		if(  (status != LV2UI_REQUEST_VALUE_SUCCESS)
			&& (status != LV2UI_REQUEST_VALUE_BUSY) )
		{
			lv2_log_error(&handle->logger, "[%s] requestValue failed: %i", __func__, status);

			if(status == LV2UI_REQUEST_VALUE_ERR_UNSUPPORTED)
			{
				handle->request_code = NULL;
			}
		}
	}
	if(d2tk_state_is_over(state) && dynparam->comment)
	if(d2tk_state_is_over(state))
	{
		d2tk_base_set_tooltip(base, sizeof(tip), tip, handle->tip_height);
	}
}
#endif

static size_t
szprintf(char *lbl, size_t lbl_len, uint32_t size) 
{
	char suffixes [4] = { ' ', 'K', 'M', 'G' };
	char *suffix = &suffixes[0];

	while(size > 1024)
	{
		d2tk_base_set_tooltip(base, -1, dynparam->comment, handle->tip_height);
		size /= 1024;
		suffix++;
	}

	return snprintf(lbl, lbl_len, "%"PRIu32"%cB",
		size, *suffix);
}

static void
_expose_slot(plughandle_t *handle, const d2tk_rect_t *rect, unsigned k)
_expose_slot_string(plughandle_t *handle, dynparam_t *dynparam,
	const d2tk_rect_t *rect, unsigned k)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);

	dynparam_t *dynparam = &handle->dynparams[k];

	if(!dynparam->label)
	if(!dynparam->val)
	{
		return;
	}

	char buf[256];
	const char *lbl;
	size_t lbl_len;

	if(dynparam->unit)
	const d2tk_coord_t frac [6] = {
		0, rect->h, rect->h, rect->h, rect->h, 0
	};
	D2TK_BASE_LAYOUT(rect, 6, frac, D2TK_FLAG_LAYOUT_X_ABS, lay)
	{
		lbl_len = snprintf(buf, sizeof(buf), "%s•%s", dynparam->label, dynparam->unit);
		lbl = buf;
		const unsigned y = d2tk_layout_get_index(lay);
		const d2tk_rect_t *lrect = d2tk_layout_get_rect(lay);

		switch(y)
		{
			case 0:
			{
				char lbl [32];
				const size_t lbl_len = szprintf(lbl, sizeof(lbl), dynparam->size);

				d2tk_base_label(base, lbl_len, lbl, 0.5f, rect,
					D2TK_ALIGN_LEFT | D2TK_ALIGN_MIDDLE);
			} break;
			case 1:
			{
				if(dynparam->writable)
				{
					_expose_dynparam_clear(handle, dynparam, lrect, k);
				}
			} break;
			case 2:
			{
				_expose_dynparam_copy(handle, dynparam,  "UTF8_STRING", lrect, k);
			} break;
			case 3:
			{
				if(dynparam->writable)
				{
					_expose_dynparam_paste(handle, dynparam, "UTF8_STRING", lrect, k);
				}
			} break;
			case 4:
			{
#ifdef _LV2_HAS_REQUEST_VALUE
				if(dynparam->writable)
				{
					_expose_blob_load(handle, dynparam->prop, dynparam->range, lrect, k);
				}
#endif
			} break;
		}
	}
	else
}

static void
_expose_slot_chunk(plughandle_t *handle, dynparam_t *dynparam,
	const d2tk_rect_t *rect, unsigned k)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);

	const d2tk_coord_t frac [6] = {
		0, rect->h, rect->h, rect->h, rect->h, 0
	};
	D2TK_BASE_LAYOUT(rect, 6, frac, D2TK_FLAG_LAYOUT_X_ABS, lay)
	{
		lbl_len = -1;
		lbl = dynparam->label;
		const unsigned y = d2tk_layout_get_index(lay);
		const d2tk_rect_t *lrect = d2tk_layout_get_rect(lay);

		switch(y)
		{
			case 0:
			{
				char lbl [32];
				const size_t lbl_len = szprintf(lbl, sizeof(lbl), dynparam->size);

				d2tk_base_label(base, lbl_len, lbl, 0.5f, rect,
					D2TK_ALIGN_LEFT | D2TK_ALIGN_MIDDLE);
			} break;
			case 1:
			{
				if(dynparam->writable)
				{
					_expose_dynparam_clear(handle, dynparam, lrect, k);
				}
			} break;
			case 2:
			{
				_expose_dynparam_copy(handle, dynparam,  "application/octet-stream", lrect, k);
			} break;
			case 3:
			{
				if(dynparam->writable)
				{
					_expose_dynparam_paste(handle, dynparam, "application/octet-stream", lrect, k);
				}
			} break;
			case 4:
			{
#ifdef _LV2_HAS_REQUEST_VALUE
				if(dynparam->writable)
				{
					_expose_blob_load(handle, dynparam->prop, dynparam->range, lrect, k);
				}
#endif
			} break;
		}
	}
}

static void
_expose_slot(plughandle_t *handle, const d2tk_rect_t *rect, unsigned k)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);

	dynparam_t *dynparam = &handle->dynparams[k];

	if(dynparam->points)
	{
		_expose_slot_enum(handle, dynparam, rect, lbl_len, lbl, k);
		_expose_slot_enum(handle, dynparam, rect, k);
	}
	else if(dynparam->range == handle->props.urid.atom_bool)
	{
		_expose_slot_bool(handle, dynparam, rect, lbl_len, lbl, k);
		_expose_slot_bool(handle, dynparam, rect, k);
	}
	else if(dynparam->range == handle->props.urid.atom_int)
	{
		_expose_slot_int(handle, dynparam, rect, lbl_len, lbl, k);
		_expose_slot_int(handle, dynparam, rect, k);
	}
	else if(dynparam->range == handle->props.urid.atom_long)
	{
		_expose_slot_long(handle, dynparam, rect, lbl_len, lbl, k);
		_expose_slot_long(handle, dynparam, rect, k);
	}
	else if(dynparam->range == handle->props.urid.atom_float)
	{
		_expose_slot_float(handle, dynparam, rect, lbl_len, lbl, k);
		_expose_slot_float(handle, dynparam, rect, k);
	}
	else if(dynparam->range == handle->props.urid.atom_double)
	{
		_expose_slot_double(handle, dynparam, rect, lbl_len, lbl, k);
		_expose_slot_double(handle, dynparam, rect, k);
	}
#if 0
	else if(dynparam->range == handle->props.urid.atom_urid)
	{
		_expose_slot_urid(handle, dynparam, rect, lbl_len, lbl, k);
		_expose_slot_urid(handle, dynparam, rect, k);
	}
	else if(dynparam->range == handle->forge.String)
	{
		_expose_slot_string(handle, dynparam, rect, lbl_len, lbl, k);
		_expose_slot_string(handle, dynparam, rect, k);
	}
	else if(dynparam->range == handle->forge.Chunk)
	{
		_expose_slot_chunk(handle, dynparam, rect, lbl_len, lbl, k);
		_expose_slot_chunk(handle, dynparam, rect, k);
	}
#endif
	else
	{
		char lbl [256];
		const size_t lbl_len = snprintf(lbl, sizeof(lbl),
			"%s (type not implemented)", dynparam->label);

		d2tk_base_label(base, lbl_len, lbl, 0.375f, rect,
			D2TK_ALIGN_RIGHT | D2TK_ALIGN_MIDDLE);
		d2tk_rect_t bnd;
		d2tk_rect_shrink_x(&bnd, rect, rect->h/2);

		d2tk_base_label(base, lbl_len, lbl, 0.375f, &bnd,
			D2TK_ALIGN_LEFT | D2TK_ALIGN_MIDDLE);
	}
}

static void
_expose_slot_frame(plughandle_t *handle, const d2tk_rect_t *rect, unsigned k)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
	const d2tk_style_t *style = d2tk_base_get_style(base);
	const uint32_t pad = 2*style->border_width;

	dynparam_t *dynparam = &handle->dynparams[k];

	if(!dynparam->label)
	{
		return;
	}

	char buf[256];
	const char *lbl;
	size_t lbl_len;

	if(dynparam->unit)
	{
		lbl_len = snprintf(buf, sizeof(buf), "%s [%s]",
			dynparam->label, dynparam->unit);
		lbl = buf;
	}
	else
	{
		lbl_len = -1;
		lbl = dynparam->label;
	}

	D2TK_BASE_FRAME(base, rect, lbl_len, lbl, frm)
	{
		const d2tk_rect_t *frect = d2tk_frame_get_rect(frm);

		const d2tk_coord_t frac [2] = { 2, 1 };
		D2TK_BASE_LAYOUT(frect, 2, frac, D2TK_FLAG_LAYOUT_Y_REL, lay)
		{
			const unsigned y = d2tk_layout_get_index(lay);
			const d2tk_rect_t *lrect = d2tk_layout_get_rect(lay);
			d2tk_rect_t bnd;

			d2tk_rect_shrink(&bnd, lrect, pad);

			switch(y)
			{
				case 0:
				{
					_expose_slot(handle, &bnd, k);
				} break;
				case 1:
				{
					if(dynparam->comment)
					{
						d2tk_base_label(base, -1, dynparam->comment, 1.f, &bnd,
							D2TK_ALIGN_RIGHT | D2TK_ALIGN_MIDDLE);
					}
				} break;
			}
		}
	}
}



@@ 1796,7 1970,7 @@ _expose_right(plughandle_t *handle, const d2tk_rect_t *rect)
				break;
			}

			_expose_slot(handle, trect, k);
			_expose_slot_frame(handle, trect, k);
		}
	}
}


@@ 1807,7 1981,8 @@ _expose_body(plughandle_t *handle, const d2tk_rect_t *rect)
	d2tk_base_t *base = d2tk_frontend_get_base(handle->dpugl);

	const d2tk_coord_t frac [3] = { 0, 5, handle->sidebar_width };
	D2TK_BASE_LAYOUT(rect, 3, frac, D2TK_FLAG_LAYOUT_X_ABS, lay)
	const size_t n = handle->ndynparams ? 3 : 1;
	D2TK_BASE_LAYOUT(rect, n, frac, D2TK_FLAG_LAYOUT_X_ABS, lay)
	{
		const unsigned k = d2tk_layout_get_index(lay);
		const d2tk_rect_t *lrect = d2tk_layout_get_rect(lay);


@@ 1972,78 2147,11 @@ _expose_text_paste(plughandle_t *handle, const d2tk_rect_t *rect)
	}
}

#ifdef _LV2_HAS_REQUEST_VALUE
static void
_expose_text_load(plughandle_t *handle, const d2tk_rect_t *rect)
{
	d2tk_frontend_t *dpugl = handle->dpugl;
	d2tk_base_t *base = d2tk_frontend_get_base(dpugl);

	static const char path [] = "save.png";
	static const char tip [] = "load from file";

	if(!handle->request_code)
	{
		return;
	}

	const d2tk_state_t state = d2tk_base_button_image(base, D2TK_ID,
		sizeof(path), path, rect);

	if(d2tk_state_is_changed(state))
	{
		const LV2_URID key = handle->urid_code;
		const LV2_URID type = handle->forge.String;

		const LV2UI_Request_Value_Status status = handle->request_code->request(
			handle->request_code->handle, key, type, NULL);

		if(  (status != LV2UI_REQUEST_VALUE_SUCCESS)
			&& (status != LV2UI_REQUEST_VALUE_BUSY) )
		{
			lv2_log_error(&handle->logger, "[%s] requestValue failed: %i", __func__, status);

			if(status == LV2UI_REQUEST_VALUE_ERR_UNSUPPORTED)
			{
				handle->request_code = NULL;
			}
		}
	}
	if(d2tk_state_is_over(state))
	{
		d2tk_base_set_tooltip(base, sizeof(tip), tip, handle->tip_height);
	}
}
#endif

static void
_expose_text_minimize(plughandle_t *handle, const d2tk_rect_t *rect)
{
	d2tk_base_t *base = d2tk_frontend_get_base(handle->dpugl);

	static const char *path [2] = { "eye-off.png", "eye.png" };
	static const char tip [2][10] = { "show code", "hide code" };

	bool visible = !handle->state.editor_hidden;
	const d2tk_state_t state = d2tk_base_toggle_label_image(base, D2TK_ID,
		0, NULL, D2TK_ALIGN_CENTERED, -1, path[visible], rect, &visible);

	if(d2tk_state_is_changed(state))
	{
		handle->state.editor_hidden = !handle->state.editor_hidden;
		_message_set_key(handle, handle->urid_editorHidden);
	}
	if(d2tk_state_is_over(state))
	{
		d2tk_base_set_tooltip(base, sizeof(tip[visible]), tip[visible], handle->tip_height);
	}
}

static void
_expose_text_footer(plughandle_t *handle, const d2tk_rect_t *rect)
{
	const d2tk_coord_t frac [8] = {
		rect->h, 0, 0, rect->h, rect->h, rect->h, rect->h, rect->h
		rect->h, rect->h, 0, 0, rect->h, rect->h, rect->h, rect->h
	};
	D2TK_BASE_LAYOUT(rect, 8, frac, D2TK_FLAG_LAYOUT_X_ABS, lay)
	{


@@ 2058,33 2166,33 @@ _expose_text_footer(plughandle_t *handle, const d2tk_rect_t *rect)
			} break;
			case 1:
			{
				_expose_text_link(handle, lrect);
				_expose_manual(handle, lrect);
			} break;
			case 2:
			{
				_expose_font_height(handle, lrect);
				_expose_text_link(handle, lrect);
			} break;
			case 3:
			{
				_expose_text_clear(handle, lrect);
				_expose_font_height(handle, lrect);
			} break;
			case 4:
			{
				_expose_text_copy(handle, lrect);
				_expose_text_clear(handle, lrect);
			} break;
			case 5:
			{
				_expose_text_paste(handle, lrect);
				_expose_text_copy(handle, lrect);
			} break;
			case 6:
			{
#ifdef _LV2_HAS_REQUEST_VALUE
				_expose_text_load(handle, lrect);
#endif
				_expose_text_paste(handle, lrect);
			} break;
			case 7:
			{
				_expose_text_minimize(handle, lrect);
#ifdef _LV2_HAS_REQUEST_VALUE
				_expose_blob_load(handle, handle->urid_code, handle->forge.String, lrect, k);
#endif
			} break;
		}
	}


@@ 2110,13 2218,12 @@ _expose(void *data, d2tk_coord_t w, d2tk_coord_t h)

	d2tk_base_set_style(base, &style);

	const d2tk_coord_t frac [4] = {
	const d2tk_coord_t frac [3] = {
		handle->header_height,
		handle->footer_height,
		0,
		handle->footer_height
	};
	D2TK_BASE_LAYOUT(&rect, 4, frac, D2TK_FLAG_LAYOUT_Y_ABS, lay)
	D2TK_BASE_LAYOUT(&rect, 3, frac, D2TK_FLAG_LAYOUT_Y_ABS, lay)
	{
		const unsigned k = d2tk_layout_get_index(lay);
		const d2tk_rect_t *lrect = d2tk_layout_get_rect(lay);


@@ 2129,13 2236,9 @@ _expose(void *data, d2tk_coord_t w, d2tk_coord_t h)
			} break;
			case 1:
			{
				_expose_graph_footer(handle, lrect);
			} break;
			case 2:
			{
				_expose_body(handle, lrect);
			} break;
			case 3:
			case 2:
			{
				_expose_text_footer(handle, lrect);
			} break;


@@ 2349,7 2452,7 @@ instantiate(const LV2UI_Descriptor *descriptor,
	handle->footer_height = 32 * handle->scale;
	handle->tip_height = 20 * handle->scale;
	handle->sidebar_width = 256 * handle->scale;
	handle->item_height = 40 * handle->scale;
	handle->item_height = 80 * handle->scale;

	handle->state.font_height = 16;
	_update_font_height(handle);