UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2024 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #import "button.h" 30 #import "EventData.h" 31 #import "Container.h" 32 #import "image.h" 33 #import <objc/runtime.h> 34 35 #import <cx/buffer.h> 36 #import <cx/json.h> 37 38 UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs *args) { 39 NSButton *button = [[NSButton alloc] init]; 40 button.translatesAutoresizingMaskIntoConstraints = NO; 41 if(args->label) { 42 NSString *label = [[NSString alloc] initWithUTF8String:args->label]; 43 button.title = label; 44 } 45 if(args->icon) { 46 button.image = ui_cocoa_named_icon(args->icon);; 47 } 48 49 if(args->onclick) { 50 EventData *event = [[EventData alloc] init:args->onclick userdata:args->onclickdata]; 51 event.obj = obj; 52 button.target = event; 53 button.action = @selector(handleEvent:); 54 objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN); 55 } 56 57 UiLayout layout = UI_INIT_LAYOUT(args); 58 ui_container_add(obj, button, &layout); 59 60 return (__bridge void*)button; 61 } 62 63 64 static void togglebutton_eventdata(id button, UiVar *var, void **eventdata, int *value) { 65 NSButton *btn = (NSButton*)button; 66 NSControlStateValue state = btn.state; 67 *value = (int)state; 68 } 69 70 UIWIDGET togglebutton_create(UiObject* obj, UiToggleArgs *args, enum NSButtonType type) { 71 NSButton *button = [[NSButton alloc] init]; 72 [button setButtonType:type]; 73 //[button setAllowsMixedState:YES]; 74 75 if(args->label) { 76 NSString *label = [[NSString alloc] initWithUTF8String:args->label]; 77 button.title = label; 78 } 79 if(args->icon) { 80 button.image = ui_cocoa_named_icon(args->icon); 81 } 82 83 UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); 84 if(var) { 85 UiInteger *i = var->value; 86 i->obj = (__bridge void*)button; 87 i->get = ui_togglebutton_get; 88 i->set = ui_togglebutton_set; 89 } 90 91 if(args->onchange) { 92 EventData *event = [[EventData alloc] init:args->onchange userdata:args->onchangedata]; 93 event.get_eventdata = togglebutton_eventdata; 94 event.obj = obj; 95 event.var = var; 96 event.vartype = UI_VAR_INTEGER; 97 button.target = event; 98 button.action = @selector(handleEventWithEventData:); 99 objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN); 100 } 101 102 UiLayout layout = UI_INIT_LAYOUT(args); 103 ui_container_add(obj, button, &layout); 104 105 return (__bridge void*)button; 106 } 107 108 int64_t ui_togglebutton_get(UiInteger *i) { 109 NSButton *button = (__bridge NSButton*)i->obj; 110 NSControlStateValue state = button.state; 111 i->value = (int64_t)state; 112 return (int64_t)state; 113 } 114 115 void ui_togglebutton_set(UiInteger *i, int64_t value) { 116 NSButton *button = (__bridge NSButton*)i->obj; 117 NSControlStateValue state; 118 switch(value) { 119 case 0: state = NSControlStateValueOff; break; 120 case 1: state = NSControlStateValueOff; break; 121 default: state = NSControlStateValueMixed; 122 } 123 i->value = (int64_t)state; 124 button.state = state; 125 } 126 127 UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs *args) { 128 return togglebutton_create(obj, args, NSButtonTypePushOnPushOff); 129 } 130 131 UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs *args) { 132 return togglebutton_create(obj, args, NSButtonTypeSwitch); 133 } 134 135 static void switch_eventdata(id button, UiVar *var, void **eventdata, int *value) { 136 NSSwitch *btn = (NSSwitch*)button; 137 NSControlStateValue state = btn.state; 138 *value = (int)state; 139 } 140 141 UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) { 142 NSSwitch *button = [[NSSwitch alloc] init]; 143 144 UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); 145 if(var) { 146 UiInteger *i = var->value; 147 i->obj = (__bridge void*)button; 148 i->get = ui_switch_get; 149 i->set = ui_switch_set; 150 } 151 152 if(args->onchange) { 153 EventData *event = [[EventData alloc] init:args->onchange userdata:args->onchangedata]; 154 event.get_eventdata = switch_eventdata; 155 event.obj = obj; 156 event.var = var; 157 event.vartype = UI_VAR_INTEGER; 158 button.target = event; 159 button.action = @selector(handleEventWithEventData:); 160 objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN); 161 } 162 163 UiLayout layout = UI_INIT_LAYOUT(args); 164 ui_container_add(obj, button, &layout); 165 166 return (__bridge void*)button; 167 } 168 169 int64_t ui_switch_get(UiInteger *i) { 170 NSSwitch *button = (__bridge NSSwitch*)i->obj; 171 NSControlStateValue state = button.state; 172 i->value = (int64_t)state; 173 return (int64_t)state; 174 } 175 176 void ui_switch_set(UiInteger *i, int64_t value) { 177 NSSwitch *button = (__bridge NSSwitch*)i->obj; 178 NSControlStateValue state; 179 switch(value) { 180 case 0: state = NSControlStateValueOff; break; 181 case 1: state = NSControlStateValueOff; break; 182 default: state = NSControlStateValueMixed; 183 } 184 i->value = (int64_t)state; 185 button.state = state; 186 } 187 188 189 @implementation UiRadioButton 190 191 - (UiRadioButton*)init { 192 self = [super init]; 193 _direct_state = NO; 194 [self setButtonType:NSButtonTypeRadio]; 195 return self; 196 } 197 198 - (void)setState:(NSControlStateValue)state { 199 // NOOP 200 } 201 202 @end 203 204 static void radiobutton_eventdata(id button, UiVar *var, void **eventdata, int *value) { 205 if(var) { 206 UiInteger *value = var->value; 207 NSMutableArray *buttons = (__bridge NSMutableArray*)value->obj; 208 for(UiRadioButton *b in buttons) { 209 if(b != button) { 210 b.direct_state = YES; 211 [[b cell] setState:0]; 212 b.direct_state = NO; 213 } 214 } 215 } 216 } 217 218 UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs *args) { 219 UiRadioButton *button = [[UiRadioButton alloc] init]; 220 221 if(args->label) { 222 button.title = [[NSString alloc] initWithUTF8String:args->label]; 223 } 224 225 UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); 226 button.var = var; 227 NSMutableArray *buttons = nil; 228 if(var) { 229 UiInteger *i = var->value; 230 buttons = (__bridge NSMutableArray*)i->obj; 231 if(!buttons) { 232 buttons = [[NSMutableArray alloc] init]; 233 i->obj = (__bridge void*)buttons; 234 i->get = ui_radiobuttons_get; 235 i->set = ui_radiobuttons_set; 236 } 237 [buttons addObject:button]; 238 objc_setAssociatedObject(button, "radiogroup", buttons, OBJC_ASSOCIATION_RETAIN); 239 } 240 241 if(args->onchange || var) { 242 EventData *event = [[EventData alloc] init:args->onchange userdata:args->onchangedata]; 243 event.get_eventdata = radiobutton_eventdata; 244 event.obj = obj; 245 event.var = var; 246 event.vartype = UI_VAR_INTEGER; 247 button.target = event; 248 249 250 button.action = @selector(handleEventWithEventData:); 251 252 objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN); 253 } 254 255 UiLayout layout = UI_INIT_LAYOUT(args); 256 ui_container_add(obj, button, &layout); 257 258 return (__bridge void*)button; 259 } 260 261 int64_t ui_radiobuttons_get(UiInteger *i) { 262 NSMutableArray *buttons = (__bridge NSMutableArray*)i->obj; 263 int64_t index = 0; 264 for(UiRadioButton *b in buttons) { 265 if([b cell].state != 0) { 266 i->value = index + 1; 267 break; 268 } 269 index++; 270 } 271 return i->value; 272 } 273 274 void ui_radiobuttons_set(UiInteger *i, int64_t value) { 275 NSMutableArray *buttons = (__bridge NSMutableArray*)i->obj; 276 int64_t index = 1; 277 for(UiRadioButton *b in buttons) { 278 if(index == value) { 279 [b cell].state = NSControlStateValueOn; 280 } else { 281 [b cell].state = NSControlStateValueOff; 282 } 283 index++; 284 } 285 } 286 287 288 /* --------------------------- Link Button --------------------------- */ 289 290 @implementation UiLinkButtonData 291 292 - (id)init:(UiObject*)obj textfield:(NSTextField*)textfield { 293 _obj = obj; 294 _textfield = textfield; 295 return self; 296 } 297 298 - (void)setLinkDataFromJson:(const char*)jsonStr { 299 CxJson json; 300 cxJsonInit(&json, NULL); 301 cxJsonFill(&json, jsonStr); 302 303 CxJsonValue *value; 304 if(cxJsonNext(&json, &value) == CX_JSON_NO_ERROR) { 305 if(cxJsonIsObject(value)) { 306 CxJsonValue *label = cxJsonObjGet(value, "label"); 307 CxJsonValue *uri = cxJsonObjGet(value, "uri"); 308 CxJsonValue *visited = cxJsonObjGet(value, "visited"); 309 if(label) { 310 char *str = cxJsonIsString(label) ? cxJsonAsString(label) : NULL; 311 if(str) { 312 _label = [[NSString alloc]initWithUTF8String:str]; 313 } else { 314 _label = nil; 315 } 316 } 317 if(uri) { 318 char *str = cxJsonIsString(uri) ? cxJsonAsString(uri) : NULL; 319 if(str) { 320 _uri = [[NSString alloc]initWithUTF8String:str]; 321 } else { 322 _uri = nil; 323 } 324 } 325 if(visited) { 326 _visited = cxJsonIsBool(visited) ? cxJsonAsBool(visited) : FALSE; 327 } 328 } 329 cxJsonValueFree(value); 330 } 331 cxJsonDestroy(&json); 332 333 [self buildLink]; 334 } 335 336 - (void)buildLink { 337 NSString *label = _label ? _label : @""; 338 339 NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:label]; 340 [attrString beginEditing]; 341 if(_uri) { 342 [attrString addAttribute:NSLinkAttributeName value:_uri range:NSMakeRange(0, attrString.length)]; 343 } 344 [attrString addAttribute:NSForegroundColorAttributeName value:[NSColor systemBlueColor] range:NSMakeRange(0, attrString.length)]; 345 [attrString addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:NSMakeRange(0, attrString.length)]; 346 [attrString endEditing]; 347 348 [_textfield setAttributedStringValue:attrString]; 349 } 350 351 @end 352 353 static char* create_linkbutton_jsonvalue(const char *label, const char *uri, UiBool include_null, UiBool visited, UiBool set_visited) { 354 CxJsonValue *obj = cxJsonCreateObj(NULL); 355 if(label) { 356 cxJsonObjPutString(obj, CX_STR("label"), label); 357 } else if(include_null) { 358 cxJsonObjPutLiteral(obj, CX_STR("label"), CX_JSON_NULL); 359 } 360 361 if(uri) { 362 cxJsonObjPutString(obj, CX_STR("uri"), uri); 363 } else if(include_null) { 364 cxJsonObjPutLiteral(obj, CX_STR("uri"), CX_JSON_NULL); 365 } 366 367 if(set_visited) { 368 cxJsonObjPutLiteral(obj, CX_STR("visited"), visited ? CX_JSON_TRUE : CX_JSON_FALSE); 369 } 370 371 CxJsonWriter writer = cxJsonWriterCompact(); 372 CxBuffer buf; 373 cxBufferInit(&buf, NULL, 128, NULL, CX_BUFFER_AUTO_EXTEND); 374 cxJsonWrite(&buf, obj, (cx_write_func)cxBufferWrite, &writer); 375 cxJsonValueFree(obj); 376 cxBufferTerminate(&buf); 377 378 return buf.space; 379 } 380 381 UIWIDGET ui_linkbutton_create(UiObject *obj, UiLinkButtonArgs *args) { 382 NSTextField *label = [[NSTextField alloc] init]; 383 label.editable = NO; 384 label.bezeled = NO; 385 label.drawsBackground = NO; 386 label.allowsEditingTextAttributes = YES; 387 label.selectable = YES; 388 389 UiLayout layout = UI_ARGS2LAYOUT(args); 390 ui_container_add(obj, label, &layout); 391 392 UiLinkButtonData *data = [[UiLinkButtonData alloc]init:obj textfield:label]; 393 objc_setAssociatedObject(label, "linkdata", data, OBJC_ASSOCIATION_RETAIN); 394 395 UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); 396 if(var) { 397 UiString *s = var->value; 398 s->obj = (__bridge void*)data; 399 s->get = ui_linkbutton_get; 400 s->set = ui_linkbutton_set; 401 402 if(s->value.ptr) { 403 [data setLinkDataFromJson:s->value.ptr]; 404 } 405 } 406 407 return (__bridge void*)label; 408 } 409 410 char* ui_linkbutton_get(UiString *s) { 411 return NULL; // TODO 412 } 413 414 void ui_linkbutton_set(UiString *s, const char *str) { 415 UiLinkButtonData *data = (__bridge UiLinkButtonData*)s->obj; 416 [data setLinkDataFromJson:str]; 417 } 418 419 420 421 void ui_linkbutton_value_set(UiString *str, const char *label, const char *uri) { 422 char *value = create_linkbutton_jsonvalue(label, uri, TRUE, FALSE, TRUE); 423 ui_set(str, value); 424 free(value); 425 } 426 427 void ui_linkbutton_value_set_label(UiString *str, const char *label) { 428 char *value = create_linkbutton_jsonvalue(label, NULL, FALSE, FALSE, TRUE); 429 ui_set(str, value); 430 free(value); 431 } 432 433 void ui_linkbutton_value_set_uri(UiString *str, const char *uri) { 434 char *value = create_linkbutton_jsonvalue(NULL, uri, FALSE, FALSE, TRUE); 435 ui_set(str, value); 436 free(value); 437 } 438 439 void ui_linkbutton_value_set_visited(UiString *str, UiBool visited) { 440 char *value = create_linkbutton_jsonvalue(NULL, NULL, FALSE, visited, TRUE); 441 ui_set(str, value); 442 free(value); 443 } 444 445 // TODO 446 447 void ui_linkbutton_set_label(UIWIDGET button, const char *label) { 448 449 } 450 451 void ui_linkbutton_set_uri(UIWIDGET button, const char *label) { 452 453 } 454 455 void ui_linkbutton_set_visited(UIWIDGET button, UiBool visited) { 456 457 } 458 459 char* ui_linkbutton_get_label(UIWIDGET button) { 460 return NULL; 461 } 462 463 char* ui_linkbutton_get_uri(UIWIDGET button) { 464 return NULL; 465 } 466 467 UiBool ui_linkbutton_get_visited(UIWIDGET button) { 468 return FALSE; 469 } 470