ObjectivelyMVC
Object oriented MVC framework for SDL3 and GNU C
Loading...
Searching...
No Matches
Slider.c
Go to the documentation of this file.
1/*
2 * ObjectivelyMVC: Object oriented MVC framework for SDL3 and C.
3 * Copyright (C) 2014 Jay Dolan <jay@jaydolan.com>
4 *
5 * This software is provided 'as-is', without any express or implied
6 * warranty. In no event will the authors be held liable for any damages
7 * arising from the use of this software.
8 *
9 * Permission is granted to anyone to use this software for any purpose,
10 * including commercial applications, and to alter it and redistribute it
11 * freely, subject to the following restrictions:
12 *
13 * 1. The origin of this software must not be misrepresented; you must not
14 * claim that you wrote the original software. If you use this software
15 * in a product, an acknowledgment in the product documentation would be
16 * appreciated but is not required.
17 *
18 * 2. Altered source versions must be plainly marked as such, and must not be
19 * misrepresented as being the original software.
20 *
21 * 3. This notice may not be removed or altered from any source distribution.
22 */
23
24#include <assert.h>
25#include <math.h>
26#include <stdlib.h>
27#include <stdio.h>
28#include <string.h>
29
30#include "Colors.h"
31#include "Log.h"
32#include "Slider.h"
33
34#define _Class _Slider
35
36#pragma mark - Object
37
41static void dealloc(Object *self) {
42
43 Slider *this = (Slider *) self;
44
45 memset(&this->delegate, 0, sizeof(this->delegate));
46
47 release(this->bar);
48 release(this->handle);
49 release(this->label);
50
51 free(this->labelFormat);
52
53 super(Object, self, dealloc);
54}
55
56#pragma mark - View
57
61static void awakeWithDictionary(View *self, const Dictionary *dictionary) {
62
63 super(View, self, awakeWithDictionary, dictionary);
64
65 Slider *this = (Slider *) self;
66
67 double value = this->value;
68
69 const Inlet inlets[] = MakeInlets(
70 MakeInlet("bar", InletTypeView, &this->bar, NULL),
71 MakeInlet("handle", InletTypeView, &this->handle, NULL),
72 MakeInlet("label", InletTypeView, &this->label, NULL),
73 MakeInlet("labelFormat", InletTypeCharacters, &this->labelFormat, NULL),
74 MakeInlet("min", InletTypeDouble, &this->min, NULL),
75 MakeInlet("max", InletTypeDouble, &this->max, NULL),
76 MakeInlet("snapToStep", InletTypeBool, &this->snapToStep, NULL),
77 MakeInlet("step", InletTypeDouble, &this->step, NULL),
78 MakeInlet("value", InletTypeDouble, &value, NULL)
79 );
80
81 $(self, bind, inlets, dictionary);
82
83 $(this, setValue, value);
84}
85
89static View *init(View *self) {
90 return (View *) $((Slider *) self, initWithFrame, NULL);
91}
92
96static void layoutSubviews(View *self) {
97
98 super(View, self, layoutSubviews);
99
100 Slider *this = (Slider *) self;
101
102 if (this->max > this->min) {
103
104 if (((View *) this->label)->hidden == false) {
105 int minWidth, maxWidth;
106 char text[64];
107
108 Text *label = (Text *) this->label;
109
110 snprintf(text, sizeof(text), this->labelFormat, this->min);
111 $(label->font, sizeCharacters, text, &minWidth, NULL);
112
113 snprintf(text, sizeof(text), this->labelFormat, this->max);
114 $(label->font, sizeCharacters, text, &maxWidth, NULL);
115
116 this->bar->frame.w -= max(minWidth, maxWidth) + label->view.padding.left;
117 }
118
119 const double fraction = clamp((this->value - this->min) / (this->max - this->min), 0.0, 1.0);
120 const SDL_Rect bounds = $(this->bar, bounds);
121
122 View *handle = (View *) this->handle;
123 handle->frame.x = (bounds.w * fraction) - handle->frame.w * 0.5;
124 } else {
125 MVC_LogWarn("max > min");
126 }
127}
128
132static void render(View *self, Renderer *renderer) {
133
134 super(View, self, render, renderer);
135
136 Slider *this = (Slider *) self;
137
138 const SDL_Rect frame = $(this->bar, renderFrame);
139
140 const SDL_Point points[] = {
141 { frame.x, frame.y + frame.h * 0.5 },
142 { frame.x + frame.w, frame.y + frame.h * 0.5 }
143 };
144
145 $(renderer, drawLine, points, &Colors.White);
146}
147
148#pragma mark - Control
149
153static bool captureEvent(Control *self, const SDL_Event *event) {
154
155 Slider *this = (Slider *) self;
156
157 const SDL_Rect frame = $((View *) this->bar, renderFrame);
158
159 if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
160
161 // The handle captures mouse motion events allowing the user to drag it
162 // However, the user may also click left or right of the handle to decrement
163 // or increment the value
164
165 if ($((View *) this->handle, didReceiveEvent, event)) {
167 } else {
168 const int x = event->button.x - frame.x;
169 const double step = this->step ?: (this->max - this->min) / 20.0;
170
171 $(this, setValue, this->value + (x > this->handle->frame.x ? step : -step));
172
173 if (this->delegate.didSetValue) {
174 this->delegate.didSetValue(this, this->value);
175 }
176 }
177 return true;
178 }
179
180 else if (event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
181 if (self->state & ControlStateHighlighted) {
182 self->state &= ~ControlStateHighlighted;
183 }
184 return true;
185 }
186
187 else if (event->type == SDL_EVENT_MOUSE_MOTION) {
188 if (self->state & ControlStateHighlighted) {
189 if (frame.w) {
190
191 const double fraction = (double) (event->motion.x - frame.x) / (double) frame.w;
192 double value = this->min + (this->max - this->min) * clamp(fraction, 0.0, 1.0);
193
194 if (this->snapToStep && this->step) {
195 value = clamp(round(value / this->step) * this->step, this->min, this->max);
196 }
197
198 const double delta = fabs(this->value - value);
199 if (delta > __DBL_EPSILON__) {
200 $(this, setValue, value);
201
202 if (this->delegate.didSetValue) {
203 this->delegate.didSetValue(this, this->value);
204 }
205 }
206
207 }
208 }
209 return true;
210 }
211
212 if (event->type == SDL_EVENT_KEY_DOWN) {
213
214 double step = 0.0;
215 switch (event->key.key) {
216 case SDLK_LEFT:
217 step = -this->step ?: -(this->max - this->min) / 20.0;
218 break;
219 case SDLK_RIGHT:
220 step = this->step ?: (this->max - this->min) / 20.0;
221 break;
222 }
223
224 if (step) {
225 $(this, setValue, this->value + step);
226
227 if (this->delegate.didSetValue) {
228 this->delegate.didSetValue(this, this->value);
229 }
230 return true;
231 }
232 }
233
234 return super(Control, self, captureEvent, event);
235}
236
237#pragma mark - Slider
238
243static void formatLabel(Slider *self) {
244
245 char text[64];
246 snprintf(text, sizeof(text), self->labelFormat, self->value);
247
248 $(self->label, setText, text);
249}
250
255static Slider *initWithFrame(Slider *self, const SDL_Rect *frame) {
256
257 self = (Slider *) super(Control, self, initWithFrame, frame);
258 if (self) {
259
260 self->bar = $(alloc(View), initWithFrame, frame);
261 assert(self->bar);
262
263 $(self->bar, addClassName, "bar");
264
265 $((View *) self, addSubview, self->bar);
266
267 self->handle = $(alloc(View), initWithFrame, NULL);
268 assert(self->handle);
269
270 $((View *) self->handle, addClassName, "handle");
271
272 $(self->bar, addSubview, (View *) self->handle);
273
274 self->label = $(alloc(Text), initWithText, NULL, NULL);
275 assert(self->label);
276
277 $((View *) self->label, addClassName, "label");
278
279 $((View *) self, addSubview, (View *) self->label);
280
281 $(self, setLabelFormat, "%0.1f");
282 }
283
284 return self;
285}
286
291static void setLabelFormat(Slider *self, const char *labelFormat) {
292
293 if (self->labelFormat) {
294 free(self->labelFormat);
295 }
296
297 self->labelFormat = strdup(labelFormat);
298 $(self, formatLabel);
299}
300
305static void setValue(Slider *self, double value) {
306
307 value = clamp(value, self->min, self->max);
308
309 const double delta = fabs(self->value - value);
310 if (delta > __DBL_EPSILON__) {
311 self->value = value;
312 self->control.view.needsLayout = true;
313
314 $(self, formatLabel);
315 }
316}
317
318#pragma mark - Class lifecycle
319
323static void initialize(Class *clazz) {
324
325 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
326
327 ((ViewInterface *) clazz->interface)->awakeWithDictionary = awakeWithDictionary;
328 ((ViewInterface *) clazz->interface)->init = init;
329 ((ViewInterface *) clazz->interface)->layoutSubviews = layoutSubviews;
330 ((ViewInterface *) clazz->interface)->render = render;
331
332 ((ControlInterface *) clazz->interface)->captureEvent = captureEvent;
333
334 ((SliderInterface *) clazz->interface)->formatLabel = formatLabel;
335 ((SliderInterface *) clazz->interface)->initWithFrame = initWithFrame;
336 ((SliderInterface *) clazz->interface)->setValue = setValue;
337 ((SliderInterface *) clazz->interface)->setLabelFormat = setLabelFormat;
338}
339
344Class *_Slider(void) {
345 static Class *clazz;
346 static Once once;
347
348 do_once(&once, {
349 clazz = _initialize(&(const ClassDef) {
350 .name = "Slider",
351 .superclass = _Control(),
352 .instanceSize = sizeof(Slider),
353 .interfaceOffset = offsetof(Slider, interface),
354 .interfaceSize = sizeof(SliderInterface),
356 });
357 });
358
359 return clazz;
360}
361
362#undef _Class
W3C Color constants.
Class * _Control(void)
Definition Control.c:391
@ ControlStateHighlighted
Definition Control.h:67
static void sizeCharacters(const Font *self, const char *chars, int *w, int *h)
Definition Font.c:273
static Label * initWithText(Label *self, const char *text, Font *font)
Definition Label.c:97
View logging facilities via SDL_Log.
#define MVC_LogWarn(fmt,...)
Definition Log.h:54
static void addSubview(View *self, View *subview)
Definition PageView.c:49
static void drawLine(const Renderer *self, const SDL_Point *points, const SDL_Color *color)
Definition Renderer.c:114
static void setValue(Slider *self, double value)
Definition Slider.c:305
static void setLabelFormat(Slider *self, const char *labelFormat)
Definition Slider.c:291
Class * _Slider(void)
Definition Slider.c:344
static View * init(View *self)
Definition Slider.c:89
static void awakeWithDictionary(View *self, const Dictionary *dictionary)
Definition Slider.c:61
static bool captureEvent(Control *self, const SDL_Event *event)
Definition Slider.c:153
static void render(View *self, Renderer *renderer)
Definition Slider.c:132
static Slider * initWithFrame(Slider *self, const SDL_Rect *frame)
Definition Slider.c:255
static void dealloc(Object *self)
Definition Slider.c:41
static void initialize(Class *clazz)
Definition Slider.c:323
static void layoutSubviews(View *self)
Definition Slider.c:96
static void formatLabel(Slider *self)
Definition Slider.c:243
A Control allowing users to drag a handle to select a numeric value.
static void setText(Text *self, const char *text)
Definition Text.c:532
@ InletTypeDouble
Definition View+JSON.h:67
@ InletTypeCharacters
Definition View+JSON.h:52
@ InletTypeBool
Definition View+JSON.h:46
@ InletTypeView
Definition View+JSON.h:139
#define MakeInlets(...)
Creates a null-termianted array of Inlets.
Definition View+JSON.h:221
#define MakeInlet(name, type, dest, data)
Creates an Inlet with the specified parameters.
Definition View+JSON.h:216
static void addClassName(View *self, const char *className)
Definition View.c:159
static bool didReceiveEvent(const View *self, const SDL_Event *event)
Definition View.c:668
static SDL_Rect renderFrame(const View *self)
Definition View.c:1455
static SDL_Rect bounds(const View *self)
Definition View.c:492
W3C Color constants.
Definition Colors.h:37
SDL_Color White
Definition Colors.h:185
Controls are Views which capture and respond to events.
Definition Control.h:83
unsigned int state
The bit mask of ControlState.
Definition Control.h:104
View view
The superclass.
Definition Control.h:88
Inlets enable inbound data binding of View attributes through JSON.
Definition View+JSON.h:155
Renderer extends Object with ObjectivelyMVC's UI rendering layer.
Definition Renderer.h:70
A Control allowing users to drag a handle to select a numeric value.
Definition Slider.h:62
double max
Definition Slider.h:104
double min
The slider bounds.
Definition Slider.h:104
View * bar
The slider bar.
Definition Slider.h:78
Text * label
The Text displaying the current value.
Definition Slider.h:94
char * labelFormat
The label format, e.g. "%0.01f".
Definition Slider.h:99
double value
The slider value.
Definition Slider.h:119
void setValue(Slider *self, double value)
Sets this Slider's value, invalidating its layout and notifying the delegate.
Definition Slider.c:305
Control control
The superclass.
Definition Slider.h:67
Text rendered with TrueType fonts.
Definition Text.h:69
View view
The superclass.
Definition Text.h:74
Font * font
The Font.
Definition Text.h:92
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
Definition View.h:134
bool needsLayout
If true, this View will layout its subviews before it is drawn.
Definition View.h:222
ViewPadding padding
The padding.
Definition View.h:234
layoutSubviews(View *self)
Performs layout for this View's immediate subviews.
Definition Box.c:74
SDL_Rect frame
The frame, relative to the superview.
Definition View.h:191
int left
Definition View.h:101