ui/cocoa/entry.m

changeset 110
c00e968d018b
--- /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;
+}

mercurial