UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2025 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 "entry.h" 30 #import <objc/runtime.h> 31 32 @implementation UiSpinBox 33 34 - (UiSpinBox*)init { 35 return self; 36 } 37 38 - (void)valueChanged { 39 float value = _stepper.doubleValue; 40 UiEvent e; 41 e.obj = _obj; 42 e.window = e.obj->window; 43 e.document = e.obj->ctx->document; 44 e.eventdata = NULL; 45 e.eventdatatype = 0; 46 e.intval = (int)value; 47 e.set = ui_get_setop(); 48 49 if(_onchange) { 50 _onchange(&e, _onchangedata); 51 } 52 53 if(_observers) { 54 UiObserver *observer = *_observers; 55 ui_notify_evt(observer, &e); 56 } 57 } 58 59 - (void)stepperChanged:(id)sender { 60 if(_isInteger) { 61 _textfield.integerValue = _stepper.integerValue; 62 } else { 63 _textfield.doubleValue = _stepper.doubleValue; 64 } 65 [self valueChanged]; 66 } 67 68 - (void) controlTextDidChange:(NSNotification *)obj { 69 if(_isInteger) { 70 _stepper.integerValue = _textfield.integerValue; 71 } else { 72 _stepper.doubleValue = _textfield.doubleValue; 73 } 74 [self valueChanged]; 75 } 76 77 @end 78 79 UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { 80 double min = args->min; 81 double max = args->max != 0 ? args->max : 1000; 82 83 UiVar *var = NULL; 84 UiVarType vartype = 0; 85 if(args->varname) { 86 var = uic_get_var(obj->ctx, args->varname); 87 if(var) { 88 vartype = var->type; 89 } else { 90 var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, args->varname, UI_VAR_RANGE); 91 vartype = UI_VAR_RANGE; 92 } 93 } 94 95 if(!var) { 96 if(args->intvalue) { 97 var = uic_widget_var(obj->ctx, obj->ctx, args->intvalue, NULL, UI_VAR_INTEGER); 98 vartype = UI_VAR_INTEGER; 99 } else if(args->doublevalue) { 100 var = uic_widget_var(obj->ctx, obj->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE); 101 vartype = UI_VAR_DOUBLE; 102 } else if(args->rangevalue) { 103 var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, NULL, UI_VAR_RANGE); 104 vartype = UI_VAR_RANGE; 105 } 106 } 107 108 if(vartype == UI_VAR_RANGE) { 109 UiRange *r = var->value; 110 min = r->min; 111 max = r->max; 112 } 113 if(args->step == 0) { 114 args->step = 1; 115 } 116 117 // create and setup textfield for number input 118 NSTextField *textfield = [[NSTextField alloc] init]; 119 textfield.translatesAutoresizingMaskIntoConstraints = NO; 120 121 if(!args->hfill || args->width > 0) { 122 textfield.translatesAutoresizingMaskIntoConstraints = NO; 123 int width = args->width > 0 ? args->width : 100; 124 [[textfield.widthAnchor constraintEqualToConstant:width] setActive:YES]; 125 } 126 127 NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; 128 formatter.numberStyle = NSNumberFormatterDecimalStyle; 129 formatter.allowsFloats = vartype != UI_VAR_INTEGER; 130 formatter.minimumFractionDigits = args->digits; 131 formatter.maximumFractionDigits = args->digits; 132 133 textfield.formatter = formatter; 134 135 // create view containing the textfield and stepper 136 NSView *view = [[NSView alloc]init]; 137 view.translatesAutoresizingMaskIntoConstraints = NO; 138 139 NSStepper *stepper = [[NSStepper alloc] init]; 140 stepper.translatesAutoresizingMaskIntoConstraints = NO; 141 142 [view addSubview:textfield]; 143 [view addSubview:stepper]; 144 145 [NSLayoutConstraint activateConstraints:@[ 146 [textfield.leadingAnchor constraintEqualToAnchor:view.leadingAnchor], 147 [textfield.topAnchor constraintEqualToAnchor:view.topAnchor], 148 [textfield.bottomAnchor constraintEqualToAnchor:view.bottomAnchor], 149 150 [stepper.trailingAnchor constraintEqualToAnchor:view.trailingAnchor], 151 [stepper.topAnchor constraintEqualToAnchor:view.topAnchor], 152 [stepper.bottomAnchor constraintEqualToAnchor:view.bottomAnchor], 153 154 [textfield.trailingAnchor constraintEqualToAnchor:stepper.leadingAnchor] 155 ]]; 156 157 UiLayout layout = UI_INIT_LAYOUT(args); 158 ui_container_add(obj, view, &layout); 159 160 // create the spinbox object, that handles all textfield and stepper events 161 UiSpinBox *spinbox = [[UiSpinBox alloc]init]; 162 spinbox.obj = obj; 163 spinbox.textfield = textfield; 164 spinbox.stepper = stepper; 165 spinbox.onchange = args->onchange; 166 spinbox.onchangedata = args->onchangedata; 167 spinbox.isInteger = vartype == UI_VAR_INTEGER; 168 objc_setAssociatedObject(stepper, "ui_spinbox", spinbox, OBJC_ASSOCIATION_RETAIN); 169 170 stepper.minValue = min; 171 stepper.maxValue = max; 172 stepper.increment = args->step; 173 stepper.target = spinbox; 174 stepper.action = @selector(stepperChanged:); 175 textfield.delegate = spinbox; 176 177 UiObserver **obs = NULL; 178 if(var) { 179 void *varObj = (__bridge void*)spinbox; 180 switch(vartype) { 181 default: break; 182 case UI_VAR_INTEGER: { 183 UiInteger *i = var->value; 184 i->get = ui_spinbutton_getint; 185 i->set = ui_spinbutton_setint; 186 i->obj = varObj; 187 obs = &i->observers; 188 189 stepper.integerValue = i->value; 190 textfield.integerValue = i->value; 191 break; 192 } 193 case UI_VAR_DOUBLE: { 194 UiDouble *d = var->value; 195 d->get = ui_spinbutton_getdouble; 196 d->set = ui_spinbutton_setdouble; 197 d->obj = varObj; 198 obs = &d->observers; 199 200 stepper.doubleValue = d->value; 201 textfield.doubleValue = d->value; 202 break; 203 } 204 case UI_VAR_RANGE: { 205 UiRange *r = var->value; 206 r->get = ui_spinbutton_getrangeval; 207 r->set = ui_spinbutton_setrangeval; 208 r->setrange = ui_spinbutton_setrange; 209 r->setextent = ui_spinbutton_setextent; 210 r->obj = varObj; 211 obs = &r->observers; 212 213 stepper.doubleValue = r->value; 214 textfield.doubleValue = r->value; 215 break; 216 } 217 } 218 } 219 spinbox.observers = obs; 220 221 return (__bridge void*)textfield; 222 } 223 224 int64_t ui_spinbutton_getint(UiInteger *i) { 225 UiSpinBox *spinbox = (__bridge UiSpinBox*)i->obj; 226 i->value = spinbox.stepper.integerValue; 227 return i->value; 228 } 229 230 void ui_spinbutton_setint(UiInteger *i, int64_t val) { 231 UiSpinBox *spinbox = (__bridge UiSpinBox*)i->obj; 232 i->value = val; 233 spinbox.stepper.integerValue = val; 234 spinbox.textfield.integerValue = val; 235 } 236 237 double ui_spinbutton_getdouble(UiDouble *d) { 238 UiSpinBox *spinbox = (__bridge UiSpinBox*)d->obj; 239 d->value = spinbox.stepper.doubleValue; 240 return d->value; 241 } 242 243 void ui_spinbutton_setdouble(UiDouble *d, double val) { 244 UiSpinBox *spinbox = (__bridge UiSpinBox*)d->obj; 245 d->value = val; 246 spinbox.stepper.doubleValue = val; 247 spinbox.textfield.doubleValue = val; 248 } 249 250 double ui_spinbutton_getrangeval(UiRange *r) { 251 UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; 252 r->value = spinbox.stepper.doubleValue; 253 return r->value; 254 } 255 256 void ui_spinbutton_setrangeval(UiRange *r, double val) { 257 UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; 258 r->value = val; 259 spinbox.stepper.doubleValue = val; 260 spinbox.textfield.doubleValue = val; 261 } 262 263 void ui_spinbutton_setrange(UiRange *r, double min, double max) { 264 UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; 265 spinbox.stepper.minValue = min; 266 spinbox.stepper.maxValue = max; 267 } 268 269 void ui_spinbutton_setextent(UiRange *r, double extent) { 270 UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; 271 spinbox.stepper.increment = extent; 272 } 273