Wed, 10 Dec 2025 22:22:55 +0100
implement all window types in the client + small fixes
/* * 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 <stdio.h> #include <stdlib.h> #include <cx/hash_map.h> #include "../ui/common/args.h" #ifndef _WIN32 static UiMessageHandler *io; static CxMap *msg_types; static CxMap *objects; void client_init(UiMessageHandler *handler) { io = handler; msg_types = cxHashMapCreateSimple(CX_STORE_POINTERS); cxMapPut(msg_types, "window", msg_window); cxMapPut(msg_types, "sidebar_window", msg_window); cxMapPut(msg_types, "splitview_window", msg_window); cxMapPut(msg_types, "simple_window", msg_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); cxMapPut(msg_types, "toggle", msg_togglebutton); 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 }; } } static UiBool jsonobj_getbool(const CxJsonValue *obj, const char *name, int *error) { CxJsonValue *value = cxJsonObjGet(obj, name); if(value->type == CX_JSON_LITERAL) { if(*error) { *error = 0; } return value->value.literal == CX_JSON_TRUE ? 1 : 0; } else { if(error) { *error = 1; } return FALSE; } } /* * 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(cxstring 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, type->value.string); } 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_window(UiObject *parent, const CxJsonValue *value, cxmutstr type) { cxmutstr obj_id = jsonobj_getstring(value, "obj"); cxmutstr id = jsonobj_getstring(value, "id"); cxmutstr title = jsonobj_getstring(value, "title"); if(!obj_id.ptr) { return 1; } if(!id.ptr) { return 1; } UiObject *obj; if(!cx_strcmp(type, "window")) { obj = ui_window(title.ptr, NULL); } else if(!cx_strcmp(type, "sidebar_window")) { obj = ui_sidebar_window(title.ptr, NULL); } else if(!cx_strcmp(type, "splitview_window")) { int err; bool sidebar = jsonobj_getbool(value, "sidebar", &err); if(err) { return 1; } obj = ui_splitview_window(title.ptr, sidebar); } else if(!cx_strcmp(type, "simple_window")) { obj = ui_simple_window(title.ptr, NULL); } client_add_obj_mapping(obj, obj_id); if(obj->widget) { client_reg_widget(obj, id, obj->widget); } return client_handle_children(obj, value); } int msg_show(UiObject *parent, const CxJsonValue *value, cxmutstr type) { 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, cxmutstr type) { return msg_container(parent, value, ui_vbox_create); } int msg_hbox(UiObject *parent, const CxJsonValue *value, cxmutstr type) { return msg_container(parent, value, ui_hbox_create); } int msg_grid(UiObject *parent, const CxJsonValue *value, cxmutstr type) { return msg_container(parent, value, ui_grid_create); } int msg_end(UiObject *parent, const CxJsonValue *value, cxmutstr type) { 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, cxmutstr type) { 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; } int msg_togglebutton(UiObject *parent, const CxJsonValue *value, cxmutstr type) { 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; } CxJsonValue *button_type = cxJsonObjGet(value, "button_type"); if(!button_type || button_type->type != CX_JSON_INTEGER) { return 1; } CxJsonValue *val = cxJsonObjGet(value, "value"); UiInteger *i = NULL; if(val && val->type == CX_JSON_STRING) { i = ui_get_int_var(obj->ctx, val->value.string.ptr); if(!i) { i = ui_int_new(obj->ctx, val->value.string.ptr); } } UiToggleArgs *args = json2toggle_args(args_value); UIWIDGET w; switch(button_type->value.integer) { default: { w = ui_togglebutton_create(obj, args); break; } case 1: { w = ui_checkbox_create(obj, args); break; } case 2: { w = ui_switch_create(obj, args); break; } case 3: { w = ui_radiobutton_create(obj, args); break; } } ui_toggle_args_free(args); client_reg_widget(obj, id, w); return 0; } #endif