--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/entry.m Sat Oct 04 14:52:59 2025 +0200 @@ -0,0 +1,272 @@ +/* + * 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. + */ + +#import "entry.h" +#import <objc/runtime.h> + +@implementation UiSpinBox + +- (UiSpinBox*)init { + return self; +} + +- (void)valueChanged { + float value = _stepper.doubleValue; + UiEvent e; + e.obj = _obj; + e.window = e.obj->window; + e.document = e.obj->ctx->document; + e.eventdata = NULL; + e.eventdatatype = 0; + e.intval = (int)value; + e.set = ui_get_setop(); + + if(_onchange) { + _onchange(&e, _onchangedata); + } + + if(_observers) { + UiObserver *observer = *_observers; + ui_notify_evt(observer, &e); + } +} + +- (void)stepperChanged:(id)sender { + if(_isInteger) { + _textfield.integerValue = _stepper.integerValue; + } else { + _textfield.doubleValue = _stepper.doubleValue; + } + [self valueChanged]; +} + +- (void) controlTextDidChange:(NSNotification *)obj { + if(_isInteger) { + _stepper.integerValue = _textfield.integerValue; + } else { + _stepper.doubleValue = _textfield.doubleValue; + } + [self valueChanged]; +} + +@end + +UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { + double min = args->min; + double max = args->max != 0 ? args->max : 1000; + + UiVar *var = NULL; + UiVarType vartype = 0; + if(args->varname) { + var = uic_get_var(obj->ctx, args->varname); + if(var) { + vartype = var->type; + } else { + var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, args->varname, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; + } + } + + if(!var) { + if(args->intvalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->intvalue, NULL, UI_VAR_INTEGER); + vartype = UI_VAR_INTEGER; + } else if(args->doublevalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE); + vartype = UI_VAR_DOUBLE; + } else if(args->rangevalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, NULL, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; + } + } + + if(vartype == UI_VAR_RANGE) { + UiRange *r = var->value; + min = r->min; + max = r->max; + } + if(args->step == 0) { + args->step = 1; + } + + // create and setup textfield for number input + NSTextField *textfield = [[NSTextField alloc] init]; + textfield.translatesAutoresizingMaskIntoConstraints = NO; + + if(!args->hfill || args->width > 0) { + textfield.translatesAutoresizingMaskIntoConstraints = NO; + int width = args->width > 0 ? args->width : 100; + [[textfield.widthAnchor constraintEqualToConstant:width] setActive:YES]; + } + + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + formatter.numberStyle = NSNumberFormatterDecimalStyle; + formatter.allowsFloats = vartype != UI_VAR_INTEGER; + formatter.minimumFractionDigits = args->digits; + formatter.maximumFractionDigits = args->digits; + + textfield.formatter = formatter; + + // create view containing the textfield and stepper + NSView *view = [[NSView alloc]init]; + view.translatesAutoresizingMaskIntoConstraints = NO; + + NSStepper *stepper = [[NSStepper alloc] init]; + stepper.translatesAutoresizingMaskIntoConstraints = NO; + + [view addSubview:textfield]; + [view addSubview:stepper]; + + [NSLayoutConstraint activateConstraints:@[ + [textfield.leadingAnchor constraintEqualToAnchor:view.leadingAnchor], + [textfield.topAnchor constraintEqualToAnchor:view.topAnchor], + [textfield.bottomAnchor constraintEqualToAnchor:view.bottomAnchor], + + [stepper.trailingAnchor constraintEqualToAnchor:view.trailingAnchor], + [stepper.topAnchor constraintEqualToAnchor:view.topAnchor], + [stepper.bottomAnchor constraintEqualToAnchor:view.bottomAnchor], + + [textfield.trailingAnchor constraintEqualToAnchor:stepper.leadingAnchor] + ]]; + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, view, &layout); + + // create the spinbox object, that handles all textfield and stepper events + UiSpinBox *spinbox = [[UiSpinBox alloc]init]; + spinbox.obj = obj; + spinbox.textfield = textfield; + spinbox.stepper = stepper; + spinbox.onchange = args->onchange; + spinbox.onchangedata = args->onchangedata; + spinbox.isInteger = vartype == UI_VAR_INTEGER; + objc_setAssociatedObject(stepper, "ui_spinbox", spinbox, OBJC_ASSOCIATION_RETAIN); + + stepper.minValue = min; + stepper.maxValue = max; + stepper.increment = args->step; + stepper.target = spinbox; + stepper.action = @selector(stepperChanged:); + textfield.delegate = spinbox; + + UiObserver **obs = NULL; + if(var) { + void *varObj = (__bridge void*)spinbox; + switch(vartype) { + default: break; + case UI_VAR_INTEGER: { + UiInteger *i = var->value; + i->get = ui_spinbutton_getint; + i->set = ui_spinbutton_setint; + i->obj = varObj; + obs = &i->observers; + + stepper.integerValue = i->value; + textfield.integerValue = i->value; + break; + } + case UI_VAR_DOUBLE: { + UiDouble *d = var->value; + d->get = ui_spinbutton_getdouble; + d->set = ui_spinbutton_setdouble; + d->obj = varObj; + obs = &d->observers; + + stepper.doubleValue = d->value; + textfield.doubleValue = d->value; + break; + } + case UI_VAR_RANGE: { + UiRange *r = var->value; + r->get = ui_spinbutton_getrangeval; + r->set = ui_spinbutton_setrangeval; + r->setrange = ui_spinbutton_setrange; + r->setextent = ui_spinbutton_setextent; + r->obj = varObj; + obs = &r->observers; + + stepper.doubleValue = r->value; + textfield.doubleValue = r->value; + break; + } + } + } + spinbox.observers = obs; + + return (__bridge void*)textfield; +} + +int64_t ui_spinbutton_getint(UiInteger *i) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)i->obj; + i->value = spinbox.stepper.integerValue; + return i->value; +} + +void ui_spinbutton_setint(UiInteger *i, int64_t val) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)i->obj; + i->value = val; + spinbox.stepper.integerValue = val; + spinbox.textfield.integerValue = val; +} + +double ui_spinbutton_getdouble(UiDouble *d) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)d->obj; + d->value = spinbox.stepper.doubleValue; + return d->value; +} + +void ui_spinbutton_setdouble(UiDouble *d, double val) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)d->obj; + d->value = val; + spinbox.stepper.doubleValue = val; + spinbox.textfield.doubleValue = val; +} + +double ui_spinbutton_getrangeval(UiRange *r) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; + r->value = spinbox.stepper.doubleValue; + return r->value; +} + +void ui_spinbutton_setrangeval(UiRange *r, double val) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; + r->value = val; + spinbox.stepper.doubleValue = val; + spinbox.textfield.doubleValue = val; +} + +void ui_spinbutton_setrange(UiRange *r, double min, double max) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; + spinbox.stepper.minValue = min; + spinbox.stepper.maxValue = max; +} + +void ui_spinbutton_setextent(UiRange *r, double extent) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; + spinbox.stepper.increment = extent; +}