aboutsummaryrefslogtreecommitdiff
path: root/canvas_ui.c
diff options
context:
space:
mode:
Diffstat (limited to 'canvas_ui.c')
-rw-r--r--canvas_ui.c575
1 files changed, 575 insertions, 0 deletions
diff --git a/canvas_ui.c b/canvas_ui.c
new file mode 100644
index 0000000..eef5d19
--- /dev/null
+++ b/canvas_ui.c
@@ -0,0 +1,575 @@
+/*
+ * Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+#include <lv2/lv2plug.in/ns/ext/patch/patch.h>
+#include <lv2/lv2plug.in/ns/extensions/ui/ui.h>
+
+#include <canvas.lv2/forge.h>
+#include <canvas.lv2/render.h>
+
+#include <pugl/pugl.h>
+
+#include <cairo/cairo.h>
+
+typedef struct _plughandle_t plughandle_t;
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_Atom_Forge forge;
+ LV2UI_Resize *host_resize;
+
+ PuglView *view;
+ int done;
+
+ LV2_Canvas canvas;
+
+ LV2_URID patch_Get;
+ LV2_URID patch_Set;
+ LV2_URID patch_Put;
+ LV2_URID patch_property;
+ LV2_URID patch_value;
+ LV2_URID patch_body;
+ LV2_URID atom_eventTransfer;
+
+ float aspect_ratio;
+ LV2_Atom_Tuple *graph;
+
+ LV2UI_Write_Function writer;
+ LV2UI_Controller controller;
+
+ union {
+ LV2_Atom atom;
+ uint8_t buf [512];
+ } dst;
+};
+
+static inline void
+_event_request(plughandle_t *handle)
+{
+ lv2_atom_forge_set_buffer(&handle->forge, handle->dst.buf, 512);
+}
+
+static inline void
+_event_commit(plughandle_t *handle)
+{
+ const uint32_t sz = lv2_atom_total_size(&handle->dst.atom);
+ handle->writer(handle->controller, 0, sz, handle->atom_eventTransfer, &handle->dst.atom);
+}
+
+static inline void
+_refresh(plughandle_t *handle)
+{
+ LV2_Atom_Forge_Frame obj_frame;
+ LV2_Atom_Forge_Ref ref;
+
+ _event_request(handle);
+
+ ref = lv2_atom_forge_object(&handle->forge, &obj_frame, 0, handle->patch_Get);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->patch_property);
+ if(ref)
+ ref = lv2_atom_forge_urid(&handle->forge, handle->canvas.urid.Canvas_aspectRatio);
+ if(ref)
+ lv2_atom_forge_pop(&handle->forge, &obj_frame);
+
+ if(ref)
+ _event_commit(handle);
+
+ _event_request(handle);
+
+ ref = lv2_atom_forge_object(&handle->forge, &obj_frame, 0, handle->patch_Get);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->patch_property);
+ if(ref)
+ ref = lv2_atom_forge_urid(&handle->forge, handle->canvas.urid.Canvas_graph);
+ if(ref)
+ lv2_atom_forge_pop(&handle->forge, &obj_frame);
+
+ if(ref)
+ _event_commit(handle);
+}
+
+static inline LV2_Atom_Forge_Ref
+_input_request(plughandle_t *handle, LV2_Atom_Forge_Frame frame [2])
+{
+ LV2_Atom_Forge_Ref ref;
+
+ _event_request(handle);
+
+ ref = lv2_atom_forge_object(&handle->forge, &frame[0], 0, handle->patch_Put);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->patch_body);
+ if(ref)
+ ref = lv2_atom_forge_object(&handle->forge, &frame[1], 0, 0);
+
+ return ref;
+}
+
+static inline void
+_input_commit(plughandle_t *handle, LV2_Atom_Forge_Frame frame [2])
+{
+ lv2_atom_forge_pop(&handle->forge, &frame[1]);
+ lv2_atom_forge_pop(&handle->forge, &frame[0]);
+
+ _event_commit(handle);
+}
+
+static inline void
+_expose(plughandle_t *handle)
+{
+#ifndef _WIN32 //FIXME
+ cairo_t *ctx = puglGetContext(handle->view);
+
+ if(ctx)
+ {
+ lv2_canvas_render(&handle->canvas, ctx, handle->graph);
+ }
+#endif
+}
+
+static inline void
+_close(plughandle_t *handle)
+{
+ handle->done = 1;
+}
+
+static inline void
+_configure(plughandle_t *handle, const PuglEventConfigure *e)
+{
+#ifndef _WIN32 //FIXME
+ cairo_t *ctx = puglGetContext(handle->view);
+
+ if(ctx)
+ {
+ cairo_surface_t *surf = cairo_get_target(ctx);
+ cairo_surface_set_device_scale(surf, e->width, e->height);
+ }
+#endif
+}
+
+static void
+_relative_position(PuglView *view, double xa, double ya, double *xr, double *yr)
+{
+ int width;
+ int height;
+
+ puglGetSize(view, &width, &height);
+
+ *xr = (double)xa / width;
+ *yr = (double)ya / height;
+}
+
+static void
+_event_func(PuglView *view, const PuglEvent *e)
+{
+ plughandle_t *handle = puglGetHandle(view);
+ LV2_Atom_Forge_Ref ref;
+ LV2_Atom_Forge_Frame frame [2];
+
+ switch(e->type)
+ {
+ case PUGL_CONFIGURE:
+ {
+ _configure(handle, (const PuglEventConfigure *)e);
+
+ puglPostRedisplay(handle->view);
+ } break;
+ case PUGL_EXPOSE:
+ {
+ _expose(handle);
+ } break;
+ case PUGL_CLOSE:
+ {
+ _close(handle);
+ } break;
+
+ case PUGL_FOCUS_IN:
+ // fall-through
+ case PUGL_FOCUS_OUT:
+ {
+ puglPostRedisplay(handle->view);
+ } break;
+
+ case PUGL_ENTER_NOTIFY:
+ case PUGL_LEAVE_NOTIFY:
+ {
+ double x, y;
+ _relative_position(view, e->crossing.x, e->crossing.y, &x, &y);
+
+ ref = _input_request(handle, frame);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->canvas.urid.Canvas_mousePositionX);
+ if(ref)
+ ref = lv2_atom_forge_double(&handle->forge, x);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->canvas.urid.Canvas_mousePositionY);
+ if(ref)
+ ref = lv2_atom_forge_double(&handle->forge, y);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->canvas.urid.Canvas_mouseFocus);
+ if(ref)
+ ref = lv2_atom_forge_bool(&handle->forge, e->type == PUGL_ENTER_NOTIFY ? 1 : 0);
+ if(ref)
+ _input_commit(handle, frame);
+
+ puglPostRedisplay(handle->view);
+ } break;
+
+ case PUGL_BUTTON_PRESS:
+ case PUGL_BUTTON_RELEASE:
+ {
+ double x, y;
+ _relative_position(view, e->button.x, e->button.y, &x, &y);
+
+ ref = _input_request(handle, frame);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->canvas.urid.Canvas_mousePositionX);
+ if(ref)
+ ref = lv2_atom_forge_double(&handle->forge, x);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->canvas.urid.Canvas_mousePositionY);
+ if(ref)
+ ref = lv2_atom_forge_double(&handle->forge, y);
+ LV2_URID button_urid;
+ switch(e->button.button)
+ {
+ case 3:
+ button_urid = handle->canvas.urid.Canvas_mouseButtonRight;
+ break;
+ case 2:
+ button_urid = handle->canvas.urid.Canvas_mouseButtonMiddle;
+ break;
+ case 1:
+ default:
+ button_urid = handle->canvas.urid.Canvas_mouseButtonLeft;
+ break;
+ }
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, button_urid);
+ if(ref)
+ ref = lv2_atom_forge_bool(&handle->forge, e->type == PUGL_BUTTON_PRESS ? 1 : 0);
+ if(ref)
+ _input_commit(handle, frame);
+ } break;
+
+ case PUGL_MOTION_NOTIFY:
+ {
+ double x, y;
+ _relative_position(view, e->motion.x, e->motion.y, &x, &y);
+
+ ref = _input_request(handle, frame);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->canvas.urid.Canvas_mousePositionX);
+ if(ref)
+ ref = lv2_atom_forge_double(&handle->forge, x);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->canvas.urid.Canvas_mousePositionY);
+ if(ref)
+ ref = lv2_atom_forge_double(&handle->forge, y);
+ if(ref)
+ _input_commit(handle, frame);
+ } break;
+
+ case PUGL_SCROLL:
+ {
+ double x, y;
+ _relative_position(view, e->scroll.x, e->scroll.y, &x, &y);
+
+ ref = _input_request(handle, frame);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->canvas.urid.Canvas_mousePositionX);
+ if(ref)
+ ref = lv2_atom_forge_double(&handle->forge, x);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->canvas.urid.Canvas_mousePositionY);
+ if(ref)
+ ref = lv2_atom_forge_double(&handle->forge, y);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->canvas.urid.Canvas_mouseWheelX);
+ if(ref)
+ ref = lv2_atom_forge_double(&handle->forge, e->scroll.dx);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->canvas.urid.Canvas_mouseWheelY);
+ if(ref)
+ ref = lv2_atom_forge_double(&handle->forge, e->scroll.dy);
+ if(ref)
+ _input_commit(handle, frame);
+ } break;
+
+ case PUGL_KEY_PRESS:
+ // fall-through
+ case PUGL_KEY_RELEASE:
+ // fall-through
+ case PUGL_NOTHING:
+ // fall-through
+ {
+ // nothing
+ } break;
+ }
+}
+
+static LV2UI_Handle
+instantiate(const LV2UI_Descriptor *descriptor, const char *plugin_uri,
+ const char *bundle_path, LV2UI_Write_Function write_function,
+ LV2UI_Controller controller, LV2UI_Widget *widget,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle= calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ return NULL;
+
+ void *parent = NULL;
+ for(int i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_UI__parent))
+ parent = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_UI__resize))
+ handle->host_resize = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = features[i]->data;
+ }
+
+ if(!parent)
+ {
+ fprintf(stderr,
+ "%s: Host does not support ui:parent\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+ if(!handle->map)
+ {
+ fprintf(stderr,
+ "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ lv2_atom_forge_init(&handle->forge, handle->map);
+
+ handle->patch_Get = handle->map->map(handle->map->handle, LV2_PATCH__Get);
+ handle->patch_Set = handle->map->map(handle->map->handle, LV2_PATCH__Set);
+ handle->patch_Put = handle->map->map(handle->map->handle, LV2_PATCH__Put);
+ handle->patch_property = handle->map->map(handle->map->handle, LV2_PATCH__property);
+ handle->patch_value = handle->map->map(handle->map->handle, LV2_PATCH__value);
+ handle->patch_body = handle->map->map(handle->map->handle, LV2_PATCH__body);
+ handle->atom_eventTransfer= handle->map->map(handle->map->handle, LV2_ATOM__eventTransfer);
+
+ handle->view = puglInit(NULL, NULL);
+ if(!handle->view)
+ {
+ free(handle);
+ return NULL;
+ }
+
+ const unsigned w = 640;
+ const unsigned h = 640;;
+ puglInitWindowClass(handle->view, "canvas");
+ puglInitWindowParent(handle->view, (intptr_t)parent);
+ puglInitWindowSize(handle->view, w, h);
+ puglInitWindowMinSize(handle->view, w/8, h/8);
+ //puglInitWindowAspectRatio(handle->view, 1, 1, 1, 1);
+ puglInitResizable(handle->view, true);
+ puglInitTransientFor(handle->view, (intptr_t)parent);
+ puglSetHandle(handle->view, handle);
+ puglSetEventFunc(handle->view, _event_func);
+
+ int stat;
+#ifdef PUGL_HAVE_GL
+ puglInitContextType(handle->view, PUGL_CAIRO_GL);
+ stat = puglCreateWindow(handle->view, "CanvasGL");
+ if(stat != 0)
+#endif
+ {
+ fprintf(stderr, "falling back to non-GL\n");
+ puglInitContextType(handle->view, PUGL_CAIRO);
+ stat = puglCreateWindow(handle->view, "Canvas");
+ }
+
+ if(stat != 0)
+ {
+ puglDestroy(handle->view);
+ free(handle);
+ return NULL;
+ }
+ puglShowWindow(handle->view);
+
+#ifndef _WIN32 //FIXME
+ cairo_t *ctx = puglGetContext(handle->view);
+ cairo_surface_t *surf = cairo_get_target(ctx);
+ cairo_surface_set_device_scale(surf, w, h);
+#endif
+
+ const intptr_t child = puglGetNativeWindow(handle->view);
+ *(intptr_t *)widget = child;
+
+ if(handle->host_resize)
+ handle->host_resize->ui_resize(handle->host_resize->handle, w, h);
+
+ lv2_canvas_init(&handle->canvas, handle->map);
+
+ handle->controller = controller;
+ handle->writer = write_function;
+ _refresh(handle);
+
+ return handle;
+}
+
+static void
+cleanup(LV2UI_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ if(handle->graph)
+ free(handle->graph);
+
+ if(handle->view)
+ {
+ if(puglGetVisible(handle->view))
+ puglHideWindow(handle->view);
+ puglDestroy(handle->view);
+ }
+
+ free(handle);
+}
+
+static void
+port_event(LV2UI_Handle instance, uint32_t index, uint32_t size,
+ uint32_t protocol, const void *buf)
+{
+ plughandle_t *handle = instance;
+
+ if(protocol == handle->atom_eventTransfer) // notify
+ {
+ const LV2_Atom_Object *obj = buf;
+
+ if(lv2_atom_forge_is_object_type(&handle->forge, obj->atom.type))
+ {
+ if(obj->body.otype == handle->patch_Set)
+ {
+ const LV2_Atom_URID *property = NULL;
+ const LV2_Atom *value = NULL;
+
+ lv2_atom_object_get(obj,
+ handle->patch_property, &property,
+ handle->patch_value, &value,
+ 0);
+
+ if( property
+ && (property->atom.type == handle->forge.URID)
+ && (property->body == handle->canvas.urid.Canvas_graph)
+ && value
+ && (value->type == handle->forge.Tuple) )
+ {
+ if(handle->graph)
+ free(handle->graph);
+
+ const size_t sz = lv2_atom_total_size(value);
+ handle->graph = malloc(sz);
+ if(handle->graph)
+ memcpy(handle->graph, value, sz);
+
+ puglPostRedisplay(handle->view);
+ }
+ else if( property
+ && (property->atom.type == handle->forge.URID)
+ && (property->body == handle->canvas.urid.Canvas_aspectRatio)
+ && value
+ && (value->type == handle->forge.Float) )
+ {
+ handle->aspect_ratio = ((const LV2_Atom_Float *)value)->body;
+
+ int w;
+ int h;
+
+ puglGetSize(handle->view, &w, &h);
+
+ int W;
+ int H;
+
+ if(handle->aspect_ratio == 0.f)
+ {
+ W = w;
+ H = h;
+ }
+ else if(handle->aspect_ratio <= 1.f)
+ {
+ W = h * handle->aspect_ratio;
+ H = h;
+ }
+ else if(handle->aspect_ratio > 1.f)
+ {
+ W = w;
+ H = w / handle->aspect_ratio;
+ }
+
+ if(handle->host_resize && ( (W != w) || (H != h) ) )
+ handle->host_resize->ui_resize(handle->host_resize->handle, W, H);
+
+ puglPostRedisplay(handle->view);
+ }
+ }
+ }
+ }
+}
+
+static int
+_idle(LV2UI_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ puglProcessEvents(handle->view);
+
+ return handle->done;
+}
+
+static const LV2UI_Idle_Interface idle_ext = {
+ .idle = _idle
+};
+
+static const void *
+extension_data(const char *uri)
+{
+ if(!strcmp(uri, LV2_UI__idleInterface))
+ return &idle_ext;
+
+ return NULL;
+}
+
+const LV2UI_Descriptor canvas_canvas_ui = {
+ .URI = "http://open-music-kontrollers.ch/lv2/canvas_display#canvas_ui",
+ .instantiate = instantiate,
+ .cleanup = cleanup,
+ .port_event = port_event,
+ .extension_data = extension_data
+};
+
+LV2_SYMBOL_EXPORT const LV2UI_Descriptor*
+lv2ui_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &canvas_canvas_ui;
+
+ default:
+ return NULL;
+ }
+}