/* * 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 #include #include #include #include #include #include #define LV2_CANVAS_RENDER_NANOVG #include 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; LV2_URID control_idx; float aspect_ratio; LV2_Atom_Tuple *graph; LV2UI_Write_Function writer; LV2UI_Controller controller; NVGcontext *ctx; unsigned w; unsigned h; 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, handle->control_idx, 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) { glViewport(0, 0, handle->w, handle->h); glClearColor(0.3f, 0.3f, 0.32f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); nvgBeginFrame(handle->ctx, handle->w, handle->h, 1.f); nvgSave(handle->ctx); nvgScale(handle->ctx, handle->w, handle->h); lv2_canvas_render(&handle->canvas, handle->ctx, handle->graph); nvgRestore(handle->ctx); nvgEndFrame(handle->ctx); } static inline void _close(plughandle_t *handle) { handle->done = 1; } static inline void _configure(plughandle_t *handle, const PuglEventConfigure *e) { handle->w = e->width; handle->h = e->height; } 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; LV2UI_Port_Map *pmap = 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; else if(!strcmp(features[i]->URI, LV2_UI__portMap)) pmap = 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; } if(!pmap) { fprintf(stderr, "%s: Host does not support ui:portMap\n", descriptor->URI); free(handle); return NULL; } handle->control_idx = pmap->port_index(pmap->handle, "control"); 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) { fprintf(stderr, "puglInit failed\n"); free(handle); return NULL; } handle->w = 640; handle->h = 640;; puglInitWindowClass(handle->view, "canvas"); puglInitWindowParent(handle->view, (intptr_t)parent); puglInitWindowSize(handle->view, handle->w, handle->h); puglInitWindowMinSize(handle->view, handle->w/8, handle->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); puglInitContextType(handle->view, PUGL_GL); const int stat = puglCreateWindow(handle->view, "CanvasGL"); if(stat != 0) { fprintf(stderr, "puglCreateWindow failed\n"); puglDestroy(handle->view); free(handle); return NULL; } puglShowWindow(handle->view); puglEnterContext(handle->view); glewExperimental = GL_TRUE; const GLenum err = glewInit(); if(err != GLEW_OK) { fprintf(stderr, "glewInit failed: %s\n", glewGetErrorString(err)); free(handle); return NULL; } handle->ctx = nvgCreate(NVG_ANTIALIAS | NVG_STENCIL_STROKES | NVG_DEBUG); if(!handle->ctx) { fprintf(stderr, "nvgCreate failed\n"); free(handle); return NULL; } puglLeaveContext(handle->view, false); const intptr_t child = puglGetNativeWindow(handle->view); *(intptr_t *)widget = child; if(handle->host_resize) handle->host_resize->ui_resize(handle->host_resize->handle, handle->w, handle->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->ctx) { puglEnterContext(handle->view); nvgDelete(handle->ctx); puglLeaveContext(handle->view, false); } 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 = CANVAS_PREFIX"display_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; } }