ObjectivelyMVC
Object oriented MVC framework for SDL3 and GNU C
Loading...
Searching...
No Matches
Slider.c File Reference
#include <assert.h>
#include <float.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <Objectively/Number.h>
#include <Objectively/String.h>
#include "Colors.h"
#include "Log.h"
#include "Slider.h"

Go to the source code of this file.

Macros

#define _Class   _Slider
 

Functions

Class * _Slider (void)
 
static void awakeWithDictionary (View *self, const Dictionary *dictionary)
 
static void bindLabels (const Inlet *inlet, ident obj)
 InletBinding for Slider::labels.
 
static void bindValues (const Inlet *inlet, ident obj)
 InletBinding Slider::values.
 
static bool captureEvent (Control *self, const SDL_Event *event)
 
static void dealloc (Object *self)
 
static void formatLabel (Slider *self)
 
static double fractionOfValue (const Slider *self)
 
static size_t indexOfValue (const Slider *self, double value)
 
static View * init (View *self)
 
static void initialize (Class *clazz)
 
static Slider * initWithFrame (Slider *self, const SDL_Rect *frame)
 
static void layoutSubviews (View *self)
 
static void render (View *self, Renderer *renderer)
 
static void setLabelFormat (Slider *self, const char *labelFormat)
 
static void setValue (Slider *self, double value)
 
static double valueAfterSteps (const Slider *self, int steps)
 
static double valueAtFraction (const Slider *self, double fraction)
 

Macro Definition Documentation

◆ _Class

#define _Class   _Slider

Definition at line 38 of file Slider.c.

Function Documentation

◆ _Slider()

Class * _Slider ( void  )

Definition at line 502 of file Slider.c.

502 {
503 static Class *clazz;
504 static Once once;
505
506 do_once(&once, {
507 clazz = _initialize(&(const ClassDef) {
508 .name = "Slider",
509 .superclass = _Control(),
510 .instanceSize = sizeof(Slider),
511 .interfaceSize = sizeof(SliderInterface),
512 .initialize = initialize,
513 });
514 });
515
516 return clazz;
517}
Class * _Control(void)
Definition Control.c:414
static void initialize(Class *clazz)
Definition Slider.c:481
A Control allowing users to drag a handle to select a numeric value.
Definition Slider.h:65

◆ awakeWithDictionary()

static void awakeWithDictionary ( View *  self,
const Dictionary *  dictionary 
)
static
See also
View::awakeWithDictionary(View *, const Dictionary *)

Definition at line 175 of file Slider.c.

175 {
176
177 super(View, self, awakeWithDictionary, dictionary);
178
179 Slider *this = (Slider *) self;
180
181 double value = this->value;
182
183 const Inlet inlets[] = MakeInlets(
184 MakeInlet("bar", InletTypeView, &this->bar, NULL),
185 MakeInlet("handle", InletTypeView, &this->handle, NULL),
186 MakeInlet("label", InletTypeView, &this->label, NULL),
187 MakeInlet("labelFormat", InletTypeCharacters, &this->labelFormat, NULL),
188 MakeInlet("labels", InletTypeApplicationDefined, &this->labels, bindLabels),
189 MakeInlet("min", InletTypeDouble, &this->min, NULL),
190 MakeInlet("max", InletTypeDouble, &this->max, NULL),
191 MakeInlet("snapToStep", InletTypeBool, &this->snapToStep, NULL),
192 MakeInlet("step", InletTypeDouble, &this->step, NULL),
193 MakeInlet("value", InletTypeDouble, &value, NULL),
194 MakeInlet("values", InletTypeApplicationDefined, &this->values, bindValues)
195 );
196
197 $(self, bind, inlets, dictionary);
198
199 if (this->labels && (!this->values || this->labels->count != this->values->count)) {
200 MVC_LogWarn("Slider labels must have the same count as values\n");
201 this->labels = release(this->labels);
202 }
203
204 $(self, setNeedsLayout);
205 $(this, setValue, value);
206
207 // setValue only reformats on a change, and the labelFormat inlet does not format at all,
208 // so a Slider whose bound value matches its initial one would keep the default format
209
210 $(this, formatLabel);
211}
#define MVC_LogWarn(fmt,...)
Definition Log.h:54
static void setValue(Slider *self, double value)
Definition Slider.c:459
static void bindLabels(const Inlet *inlet, ident obj)
InletBinding for Slider::labels.
Definition Slider.c:160
static void awakeWithDictionary(View *self, const Dictionary *dictionary)
Definition Slider.c:175
static void bindValues(const Inlet *inlet, ident obj)
InletBinding Slider::values.
Definition Slider.c:136
static void formatLabel(Slider *self)
Definition Slider.c:390
@ InletTypeDouble
Definition View+JSON.h:67
@ InletTypeCharacters
Definition View+JSON.h:52
@ InletTypeBool
Definition View+JSON.h:46
@ InletTypeApplicationDefined
Definition View+JSON.h:139
@ InletTypeView
Definition View+JSON.h:133
#define MakeInlets(...)
Creates a null-termianted array of Inlets.
Definition View+JSON.h:215
#define MakeInlet(name, type, dest, data)
Creates an Inlet with the specified parameters.
Definition View+JSON.h:210
static void setNeedsLayout(View *self)
Definition View.c:1865
Inlets enable inbound data binding of View attributes through JSON.
Definition View+JSON.h:149
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
Definition View.h:225

◆ bindLabels()

static void bindLabels ( const Inlet *  inlet,
ident  obj 
)
static

InletBinding for Slider::labels.

Definition at line 160 of file Slider.c.

160 {
161
162 const Array *array = cast(Array, obj);
163 for (size_t i = 0; i < array->count; i++) {
164 cast(String, $(array, objectAtIndex, i));
165 }
166
167 Array *labels = cast(Array, $((Object *) array, copy));
168 release(*(Array **) inlet->dest);
169 *(Array **) inlet->dest = labels;
170}
ident dest
The Inlet destination.
Definition View+JSON.h:164

◆ bindValues()

static void bindValues ( const Inlet *  inlet,
ident  obj 
)
static

InletBinding Slider::values.

Definition at line 136 of file Slider.c.

136 {
137
138 const Array *array = cast(Array, obj);
139 assert(array->count);
140
141 Vector *values = $(alloc(Vector), initWithSize, sizeof(double));
142 assert(values);
143
144 for (size_t i = 0; i < array->count; i++) {
145 double value = cast(Number, $(array, objectAtIndex, i))->value;
146 $(values, add, &value);
147 }
148
149 if (values->count < 2) {
150 MVC_LogWarn("Inlet %s wanted 2 or more values but received %zd\n", inlet->name, values->count);
151 }
152
153 release(*(Vector **) inlet->dest);
154 *(Vector **) inlet->dest = values;
155}
const char * name
The Inlet name, e.g. "alignment".
Definition View+JSON.h:154

◆ captureEvent()

static bool captureEvent ( Control *  self,
const SDL_Event *  event 
)
static
See also
Control::captureEvent(Control *, const SDL_Event *)

Definition at line 305 of file Slider.c.

305 {
306
307 Slider *this = (Slider *) self;
308
309 const SDL_Rect frame = $((View *) this->bar, renderFrame);
310
311 if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
312
313 // The handle captures mouse motion events allowing the user to drag it
314 // However, the user may also click left or right of the handle to decrement
315 // or increment the value
316
317 if ($((View *) this->handle, didReceiveEvent, event)) {
319 } else {
320 const int x = event->button.x - frame.x;
321
322 $(this, setValue, valueAfterSteps(this, x > this->handle->frame.x ? 1 : -1));
323
324 if (this->delegate.didSetValue) {
325 this->delegate.didSetValue(this, this->value);
326 }
327 }
328 return true;
329 }
330
331 else if (event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
332 if (self->state & ControlStateHighlighted) {
333 self->state &= ~ControlStateHighlighted;
334 }
335 return true;
336 }
337
338 else if (event->type == SDL_EVENT_MOUSE_MOTION) {
339 if (self->state & ControlStateHighlighted) {
340 if (frame.w) {
341
342 const double fraction = (double) (event->motion.x - frame.x) / (double) frame.w;
343 const double value = valueAtFraction(this, fraction);
344
345 const double delta = fabs(this->value - value);
346 if (delta > __DBL_EPSILON__) {
347 $(this, setValue, value);
348
349 if (this->delegate.didSetValue) {
350 this->delegate.didSetValue(this, this->value);
351 }
352 }
353
354 }
355 }
356 return true;
357 }
358
359 if (event->type == SDL_EVENT_KEY_DOWN) {
360
361 int steps = 0;
362 switch (event->key.key) {
363 case SDLK_LEFT:
364 steps = -1;
365 break;
366 case SDLK_RIGHT:
367 steps = 1;
368 break;
369 }
370
371 if (steps) {
372 $(this, setValue, valueAfterSteps(this, steps));
373
374 if (this->delegate.didSetValue) {
375 this->delegate.didSetValue(this, this->value);
376 }
377 return true;
378 }
379 }
380
381 return super(Control, self, captureEvent, event);
382}
@ ControlStateHighlighted
Definition Control.h:67
static bool captureEvent(Control *self, const SDL_Event *event)
Definition Slider.c:305
static double valueAfterSteps(const Slider *self, int steps)
Definition Slider.c:97
static double valueAtFraction(const Slider *self, double fraction)
Definition Slider.c:77
static bool didReceiveEvent(const View *self, const SDL_Event *event)
Definition View.c:726
static SDL_Rect renderFrame(const View *self)
Definition View.c:1629
Controls are Views which capture and respond to events.
Definition Control.h:83
unsigned int state
The bit mask of ControlState.
Definition Control.h:117
SDL_Rect frame
The frame, relative to the superview.
Definition View.h:301

◆ dealloc()

static void dealloc ( Object *  self)
static
See also
Object::dealloc(Object *)

Definition at line 114 of file Slider.c.

114 {
115
116 Slider *this = (Slider *) self;
117
118 memset(&this->delegate, 0, sizeof(this->delegate));
119
120 release(this->bar);
121 release(this->handle);
122 release(this->label);
123 release(this->values);
124 release(this->labels);
125
126 free(this->labelFormat);
127
128 super(Object, self, dealloc);
129}
static void dealloc(Object *self)
Definition Slider.c:114

◆ formatLabel()

static void formatLabel ( Slider *  self)
static

Definition at line 390 of file Slider.c.

390 {
391
392 if (self->labels) {
393 assert(self->values && self->labels->count == self->values->count);
394 const String *label = cast(String, $(self->labels, objectAtIndex, indexOfValue(self, self->value)));
395 $(self->label, setText, label->chars);
396 return;
397 }
398
399 char text[64];
400 snprintf(text, sizeof(text), self->labelFormat, self->value);
401
402 $(self->label, setText, text);
403}
static size_t indexOfValue(const Slider *self, double value)
Definition Slider.c:43
static void setText(Text *self, const char *text)
Definition Text.c:899
Vector * values
An optional Vector of non-linear values.
Definition Slider.h:135
Array * labels
An optional Array of non-linear labels parallel to values.
Definition Slider.h:108
Text * label
The Text displaying the current value.
Definition Slider.h:97
char * labelFormat
The label format, e.g. "%0.01f".
Definition Slider.h:102
double value
The slider value.
Definition Slider.h:128

◆ fractionOfValue()

static double fractionOfValue ( const Slider *  self)
static
Returns
The handle position, 0.0 to 1.0, of this Slider's current value.

Definition at line 62 of file Slider.c.

62 {
63
64 if (self->values) {
65 if (self->values->count < 2) {
66 return 0.0;
67 }
68 return indexOfValue(self, self->value) / (double) (self->values->count - 1);
69 }
70
71 return clamp((self->value - self->min) / (self->max - self->min), 0.0, 1.0);
72}
double max
Definition Slider.h:113
double min
The slider bounds.
Definition Slider.h:113

◆ indexOfValue()

static size_t indexOfValue ( const Slider *  self,
double  value 
)
static
Returns
The index in values nearest to the given value.

Definition at line 43 of file Slider.c.

43 {
44
45 size_t index = 0;
46 double nearest = DBL_MAX;
47
48 for (size_t i = 0; i < self->values->count; i++) {
49 const double delta = fabs(VectorValue(self->values, double, i) - value);
50 if (delta < nearest) {
51 nearest = delta;
52 index = i;
53 }
54 }
55
56 return index;
57}

◆ init()

static View * init ( View *  self)
static
See also
View::init(View *)

Definition at line 216 of file Slider.c.

216 {
217 return (View *) $((Slider *) self, initWithFrame, NULL);
218}
static Slider * initWithFrame(Slider *self, const SDL_Rect *frame)
Definition Slider.c:409

◆ initialize()

static void initialize ( Class *  clazz)
static
See also
Class::initialize(Class *)

Definition at line 481 of file Slider.c.

481 {
482
483 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
484
485 ((ViewInterface *) clazz->interface)->awakeWithDictionary = awakeWithDictionary;
486 ((ViewInterface *) clazz->interface)->init = init;
487 ((ViewInterface *) clazz->interface)->layoutSubviews = layoutSubviews;
488 ((ViewInterface *) clazz->interface)->render = render;
489
490 ((ControlInterface *) clazz->interface)->captureEvent = captureEvent;
491
492 ((SliderInterface *) clazz->interface)->formatLabel = formatLabel;
493 ((SliderInterface *) clazz->interface)->initWithFrame = initWithFrame;
494 ((SliderInterface *) clazz->interface)->setValue = setValue;
495 ((SliderInterface *) clazz->interface)->setLabelFormat = setLabelFormat;
496}
static void setLabelFormat(Slider *self, const char *labelFormat)
Definition Slider.c:445
static View * init(View *self)
Definition Slider.c:216
static void render(View *self, Renderer *renderer)
Definition Slider.c:284
static void layoutSubviews(View *self)
Definition Slider.c:223
void setValue(Slider *self, double value)
Sets this Slider's value, invalidating its layout and notifying the delegate.
Definition Slider.c:459
layoutSubviews(View *self)
Arranges this View's immediate subviews within its (already final) frame.
Definition Box.c:74

◆ initWithFrame()

static Slider * initWithFrame ( Slider *  self,
const SDL_Rect *  frame 
)
static

Definition at line 409 of file Slider.c.

409 {
410
411 self = (Slider *) super(Control, self, initWithFrame, frame);
412 if (self) {
413
414 self->bar = $(alloc(View), initWithFrame, frame);
415 assert(self->bar);
416
417 $(self->bar, addClassName, "bar");
418
419 $((View *) self, addSubview, self->bar);
420
421 self->handle = $(alloc(View), initWithFrame, NULL);
422 assert(self->handle);
423
424 $((View *) self->handle, addClassName, "handle");
425
426 $(self->bar, addSubview, (View *) self->handle);
427
428 self->label = $(alloc(Text), initWithText, NULL, NULL);
429 assert(self->label);
430
431 $((View *) self->label, addClassName, "label");
432
433 $((View *) self, addSubview, (View *) self->label);
434
435 $(self, setLabelFormat, "%0.1f");
436 }
437
438 return self;
439}
static Label * initWithText(Label *self, const char *text, Font *font)
Definition Label.c:97
static void addSubview(View *self, View *subview)
Definition PageView.c:49
static void addClassName(View *self, const char *className)
Definition View.c:183
View * bar
The slider bar.
Definition Slider.h:81
Text rendered with TrueType fonts.
Definition Text.h:131

◆ layoutSubviews()

static void layoutSubviews ( View *  self)
static
See also
View::layoutSubviews(View *)

Definition at line 223 of file Slider.c.

223 {
224
225 super(View, self, layoutSubviews);
226
227 Slider *this = (Slider *) self;
228
229 if (this->values || this->max > this->min) {
230
231 if (((View *) this->label)->visibility != ViewVisibilityHidden) {
232 int labelWidth = 0;
233 char text[64];
234
235 Text *label = (Text *) this->label;
236
237 // the widest entry is not necessarily an extremity, e.g. "0.25" over "3"
238
239 if (this->values) {
240 for (size_t i = 0; i < this->values->count; i++) {
241 int width;
242
243 const char *characters;
244 if (this->labels) {
245 characters = cast(String, $(this->labels, objectAtIndex, i))->chars;
246 } else {
247 snprintf(text, sizeof(text), this->labelFormat, VectorValue(this->values, double, i));
248 characters = text;
249 }
250 $(label->font, sizeCharacters, characters, &width, NULL);
251
252 labelWidth = max(labelWidth, width);
253 }
254 } else {
255 int minWidth, maxWidth;
256
257 snprintf(text, sizeof(text), this->labelFormat, this->min);
258 $(label->font, sizeCharacters, text, &minWidth, NULL);
259
260 snprintf(text, sizeof(text), this->labelFormat, this->max);
261 $(label->font, sizeCharacters, text, &maxWidth, NULL);
262
263 labelWidth = max(minWidth, maxWidth);
264 }
265
266 this->bar->frame.w -= labelWidth + label->view.padding.left;
267 }
268
269 const double fraction = fractionOfValue(this);
270 const SDL_Rect bounds = $(this->bar, bounds);
271
272 View *handle = (View *) this->handle;
273 handle->frame.x = (bounds.w * fraction) - handle->frame.w * 0.5;
274 } else {
275 if (this->max < this->min) {
276 MVC_LogWarn("max must be greater than min");
277 }
278 }
279}
static void sizeCharacters(const Font *self, const char *chars, int *w, int *h)
Definition Font.c:352
static double fractionOfValue(const Slider *self)
Definition Slider.c:62
static SDL_Rect bounds(const View *self)
Definition View.c:552
@ ViewVisibilityHidden
Definition View.h:122
View view
The superclass.
Definition Text.h:136
Font * font
The Font. A fixed-width Font carries a bitmap, and is drawn as one quad per glyph from its glyph shee...
Definition Text.h:157
ViewPadding padding
The padding.
Definition View.h:398
int left
Definition View.h:170

◆ render()

static void render ( View *  self,
Renderer *  renderer 
)
static
See also
View::render(View *, Renderer *)

Definition at line 284 of file Slider.c.

284 {
285
286 super(View, self, render, renderer);
287
288 Slider *this = (Slider *) self;
289
290 const SDL_Rect frame = $(this->bar, renderFrame);
291
292 const SDL_Point points[] = {
293 { frame.x, frame.y + frame.h * 0.5 },
294 { frame.x + frame.w, frame.y + frame.h * 0.5 }
295 };
296
297 $(renderer, drawLine, points, &Colors.White);
298}
static void drawLine(const Renderer *self, const SDL_Point *points, const SDL_Color *color)
Definition Renderer.c:266
W3C Color constants.
Definition Colors.h:37
SDL_Color White
Definition Colors.h:185

◆ setLabelFormat()

static void setLabelFormat ( Slider *  self,
const char *  labelFormat 
)
static

Definition at line 445 of file Slider.c.

445 {
446
447 if (self->labelFormat) {
448 free(self->labelFormat);
449 }
450
451 self->labelFormat = strdup(labelFormat);
452 $(self, formatLabel);
453}

◆ setValue()

static void setValue ( Slider *  self,
double  value 
)
static

Definition at line 459 of file Slider.c.

459 {
460
461 if (self->values) {
462 value = VectorValue(self->values, double, indexOfValue(self, value));
463 } else {
464 value = clamp(value, self->min, self->max);
465 }
466
467 const double delta = fabs(self->value - value);
468 if (delta > __DBL_EPSILON__) {
469 self->value = value;
470 $((View *) self, setNeedsLayout);
471
472 $(self, formatLabel);
473 }
474}

◆ valueAfterSteps()

static double valueAfterSteps ( const Slider *  self,
int  steps 
)
static
Returns
The value steps increments away from this Slider's current value.

Definition at line 97 of file Slider.c.

97 {
98
99 if (self->values) {
100 const ssize_t index = clamp((ssize_t) indexOfValue(self, self->value) + steps,
101 (ssize_t) 0, (ssize_t) self->values->count - 1);
102 return VectorValue(self->values, double, index);
103 }
104
105 const double step = self->step ?: (self->max - self->min) / 20.0;
106 return self->value + step * steps;
107}
double step
The step to increment on key events.
Definition Slider.h:118

◆ valueAtFraction()

static double valueAtFraction ( const Slider *  self,
double  fraction 
)
static
Returns
The value at the given handle position, snapped.

Definition at line 77 of file Slider.c.

77 {
78
79 fraction = clamp(fraction, 0.0, 1.0);
80
81 if (self->values) {
82 return VectorValue(self->values, double, (size_t) round(fraction * (self->values->count - 1)));
83 }
84
85 const double value = self->min + (self->max - self->min) * fraction;
86
87 if (self->snapToStep && self->step) {
88 return clamp(round(value / self->step) * self->step, self->min, self->max);
89 }
90
91 return value;
92}
bool snapToStep
Enable snapping the slider handle to the step.
Definition Slider.h:123