| |
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 } |