--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/uiclient.c Sun Nov 30 18:15:46 2025 +0100 @@ -0,0 +1,254 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "uiclient.h" +#include "args.h" + +#include <cx/hash_map.h> + +#include "../ui/common/args.h" + +static MessageHandler *io; + +static CxMap *msg_types; +static CxMap *objects; + +void client_init(MessageHandler *handler) { + io = handler; + + msg_types = cxHashMapCreateSimple(CX_STORE_POINTERS); + cxMapPut(msg_types, "simple_window", msg_simple_window); + cxMapPut(msg_types, "show", msg_show); + + cxMapPut(msg_types, "vbox", msg_vbox); + cxMapPut(msg_types, "hbox", msg_vbox); + cxMapPut(msg_types, "grid", msg_vbox); + cxMapPut(msg_types, "end", msg_end); + cxMapPut(msg_types, "button", msg_button); + + objects = cxHashMapCreateSimple(CX_STORE_POINTERS); +} + +static cxmutstr jsonobj_getstring(const CxJsonValue *obj, const char *name) { + CxJsonValue *value = cxJsonObjGet(obj, name); + if(value->type == CX_JSON_STRING) { + return value->value.string; + } else { + return (cxmutstr){ NULL, 0 }; + } +} + +/* + * UI thread callback + */ +static int msg_received(void *data) { + CxJsonValue *value = data; + if(client_handle_json(NULL, value)) { + fprintf(stderr, "Error: invalid json message\n"); + } + cxJsonValueFree(value); + return 0; +} + +/* + * This message callback is executed in the message handler input thread + */ +void client_msg_received(cxmutstr msg) { + // parse message + CxJson json; + cxJsonInit(&json, NULL); + + cxJsonFilln(&json, msg.ptr, msg.length); + CxJsonValue *value; + if(cxJsonNext(&json, &value) == CX_JSON_NO_ERROR && value) { + // handle json message in the UI thread + ui_call_mainthread(msg_received, value); + } else { + fprintf(stderr, "Error: invalid json message\n"); + } + cxJsonDestroy(&json); +} + +int client_handle_json(UiObject *obj, const CxJsonValue *value) { + if(value->type != CX_JSON_OBJECT) { + return 1; + } + + CxJsonValue *type = cxJsonObjGet(value, "type"); + if(!type || type->type != CX_JSON_STRING) { + return 1; + } + + json_msg_handler handler = cxMapGet(msg_types, type->value.string); + if(!handler) { + return 1; + } + + return handler(obj, value); +} + +int client_handle_children(UiObject *parent, const CxJsonValue *value) { + CxJsonValue *children = cxJsonObjGet(value, "children"); + if(children && children->type == CX_JSON_ARRAY) { + for(int i=0;i<children->value.array.array_size;i++) { + CxJsonValue *child = children->value.array.array[i]; + if(client_handle_json(parent, child)) { + fprintf(stderr, "Error: invalid child\n"); + return 1; + } + } + } + return 0; +} + +void client_add_obj_mapping(UiObject *obj, cxmutstr id) { + cxMapPut(objects, id, obj); + + CxAllocator *a = ui_allocator(obj->ctx); + + WindowData *wdata = cxMalloc(a, sizeof(WindowData)); + wdata->widgets = cxHashMapCreate(a, CX_STORE_POINTERS, 128); + obj->window = wdata; +} + +UiObject* client_get_mapped_obj(cxmutstr id) { + return cxMapGet(objects, id); +} + +void client_reg_widget(UiObject *obj, cxmutstr id, UIWIDGET w) { + WindowData *wdata = obj->window; + if(!wdata) { + fprintf(stderr, "Error: missing obj window data\n"); + return; + } + cxMapPut(wdata->widgets, id, w); +} + +UIWIDGET client_get_widget(UiObject *obj, cxmutstr id) { + WindowData *wdata = obj->window; + if(!wdata) { + fprintf(stderr, "Error: missing obj window data\n"); + return NULL; + } + return cxMapGet(wdata->widgets, id); +} + +static UiObject* get_msg_obj(UiObject *obj, const CxJsonValue *value) { + if(obj) { + return obj; + } + CxJsonValue *obj_id = cxJsonObjGet(value, "obj"); + if(!obj_id || obj_id->type != CX_JSON_STRING) { + return NULL; + } + return client_get_mapped_obj(obj_id->value.string); +} + +int msg_simple_window(UiObject *parent, const CxJsonValue *value) { + cxmutstr id = jsonobj_getstring(value, "id"); + cxmutstr title = jsonobj_getstring(value, "title"); + + if(!id.ptr) { + return 1; + } + + UiObject *obj = ui_simple_window(title.ptr, NULL); + client_add_obj_mapping(obj, id); + + return client_handle_children(obj, value); +} + +int msg_show(UiObject *parent, const CxJsonValue *value) { + UiObject *obj = client_get_mapped_obj(jsonobj_getstring(value, "obj")); + if(!obj) { + return 1; + } + ui_show(obj); + return 0; +} + +typedef UIWIDGET(*ctcreate_func)(UiObject *obj, UiContainerArgs *args); + +static int msg_container(UiObject *parent, const CxJsonValue *value, ctcreate_func create) { + CxJsonValue *args_value = cxJsonObjGet(value, "args"); + cxmutstr id = jsonobj_getstring(value, "id"); + if(!id.ptr) { + return 1; + } + UiObject *obj = get_msg_obj(parent, value); + if(!obj) { + return 1; + } + + UiContainerArgs *args = json2container_args(args_value); + UIWIDGET w = create(obj, args); + ui_container_args_free(args); + client_reg_widget(obj, id, w); + + return 0; +} + +int msg_vbox(UiObject *parent, const CxJsonValue *value) { + return msg_container(parent, value, ui_vbox_create); +} + +int msg_hbox(UiObject *parent, const CxJsonValue *value) { + return msg_container(parent, value, ui_hbox_create); +} + +int msg_grid(UiObject *parent, const CxJsonValue *value) { + return msg_container(parent, value, ui_grid_create); +} + +int msg_end(UiObject *parent, const CxJsonValue *value) { + UiObject *obj = get_msg_obj(parent, value); + if(!obj) { + return 1; + } + ui_end_new(obj); + return 0; +} + +int msg_button(UiObject *parent, const CxJsonValue *value) { + CxJsonValue *args_value = cxJsonObjGet(value, "args"); + cxmutstr id = jsonobj_getstring(value, "id"); + if(!id.ptr) { + return 1; + } + UiObject *obj = get_msg_obj(parent, value); + if(!obj) { + return 1; + } + + UiButtonArgs *args = json2button_args(args_value); + UIWIDGET w = ui_button_create(obj, args); + ui_button_args_free(args); + client_reg_widget(obj, id, w); + + return 0; +}