ObjectivelyMVC 0.1.0
Object oriented MVC framework for OpenGL, SDL2 and GNU C
Slider.c
Go to the documentation of this file.
1/*
2 * ObjectivelyMVC: Object oriented MVC framework for OpenGL, SDL2 and GNU 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 <string.h>
26
27#include "Log.h"
28#include "Slider.h"
29
30#define _Class _Slider
31
32#pragma mark - Object
33
37static void dealloc(Object *self) {
38
39 Slider *this = (Slider *) self;
40
41 release(this->bar);
42 release(this->handle);
43 release(this->label);
44
45 free(this->labelFormat);
46
47 super(Object, self, dealloc);
48}
49
50#pragma mark - View
51
55static void awakeWithDictionary(View *self, const Dictionary *dictionary) {
56
57 super(View, self, awakeWithDictionary, dictionary);
58
59 Slider *this = (Slider *) self;
60
61 double value = this->value;
62
63 const Inlet inlets[] = MakeInlets(
64 MakeInlet("bar", InletTypeView, &this->bar, NULL),
65 MakeInlet("handle", InletTypeView, &this->handle, NULL),
66 MakeInlet("label", InletTypeView, &this->label, NULL),
67 MakeInlet("labelFormat", InletTypeCharacters, &this->labelFormat, NULL),
68 MakeInlet("min", InletTypeDouble, &this->min, NULL),
69 MakeInlet("max", InletTypeDouble, &this->max, NULL),
70 MakeInlet("step", InletTypeDouble, &this->step, NULL),
71 MakeInlet("value", InletTypeDouble, &value, NULL)
72 );
73
74 $(self, bind, inlets, dictionary);
75
76 $(this, setValue, value);
77}
78
82static View *init(View *self) {
83 return (View *) $((Slider *) self, initWithFrame, NULL);
84}
85
89static void layoutSubviews(View *self) {
90
91 super(View, self, layoutSubviews);
92
93 Slider *this = (Slider *) self;
94
95 if (this->max > this->min) {
96
97 if (((View *) this->label)->hidden == false) {
98 int minWidth, maxWidth;
99 char text[64];
100
101 Text *label = (Text *) this->label;
102
103 snprintf(text, sizeof(text), this->labelFormat, this->min);
104 $(label->font, sizeCharacters, text, &minWidth, NULL);
105
106 snprintf(text, sizeof(text), this->labelFormat, this->max);
107 $(label->font, sizeCharacters, text, &maxWidth, NULL);
108
109 this->bar->frame.w -= max(minWidth, maxWidth) + label->view.padding.left;
110 }
111
112 const double fraction = clamp((this->value - this->min) / (this->max - this->min), 0.0, 1.0);
113 const SDL_Rect bounds = $(this->bar, bounds);
114
115 View *handle = (View *) this->handle;
116 handle->frame.x = (bounds.w * fraction) - handle->frame.w * 0.5;
117 } else {
118 MVC_LogWarn("max > min");
119 }
120}
121
125static void render(View *self, Renderer *renderer) {
126
127 super(View, self, render, renderer);
128
129 Slider *this = (Slider *) self;
130
131 const SDL_Rect frame = $(this->bar, renderFrame);
132
133 const SDL_Point points[] = {
134 { frame.x, frame.y + frame.h * 0.5 },
135 { frame.x + frame.w, frame.y + frame.h * 0.5 }
136 };
137
138 $(renderer, drawLine, points);
139}
140
141#pragma mark - Control
142
146static _Bool captureEvent(Control *self, const SDL_Event *event) {
147
148 Slider *this = (Slider *) self;
149
150 if (event->type == SDL_MOUSEBUTTONDOWN) {
151 if ($((View *) this->handle, didReceiveEvent, event)) {
153 }
154 }
155
156 else if (event->type == SDL_MOUSEBUTTONUP) {
157 if (self->state & ControlStateHighlighted) {
158 self->state &= ~ControlStateHighlighted;
159 }
160 }
161
162 else if (event->type == SDL_MOUSEMOTION) {
163 if (self->state & ControlStateHighlighted) {
164 const SDL_Rect frame = $((View *) this->bar, renderFrame);
165 if (frame.w) {
166
167 const double fraction = (event->motion.x - frame.x) / (double) frame.w;
168 double value = this->min + (this->max - this->min) * clamp(fraction, 0.0, 1.0);
169
170 if (this->snapToStep && this->step) {
171 value = clamp(round(value / this->step) * this->step, this->min, this->max);
172 }
173
174 const double delta = fabs(this->value - value);
175 if (delta > __DBL_EPSILON__) {
176 $(this, setValue, value);
177
178 if (this->delegate.didSetValue) {
179 this->delegate.didSetValue(this, this->value);
180 }
181 }
182
183 return true;
184 }
185 }
186 }
187
188 return super(Control, self, captureEvent, event);
189}
190
191#pragma mark - Slider
192
197static void formatLabel(Slider *self) {
198
199 char text[64];
200 snprintf(text, sizeof(text), self->labelFormat, self->value);
201
202 $(self->label, setText, text);
203}
204
209static Slider *initWithFrame(Slider *self, const SDL_Rect *frame) {
210
211 self = (Slider *) super(Control, self, initWithFrame, frame);
212 if (self) {
213
214 self->bar = $(alloc(View), initWithFrame, frame);
215 assert(self->bar);
216
217 $(self->bar, addClassName, "bar");
218
219 $((View *) self, addSubview, self->bar);
220
221 self->handle = $(alloc(Control), initWithFrame, NULL);
222 assert(self->handle);
223
224 $((View *) self->handle, addClassName, "handle");
225
226 $(self->bar, addSubview, (View *) self->handle);
227
228 self->label = $(alloc(Text), initWithText, NULL, NULL);
229 assert(self->label);
230
231 $((View *) self->label, addClassName, "label");
232
233 $((View *) self, addSubview, (View *) self->label);
234
235 $(self, setLabelFormat, "%0.1f");
236 }
237
238 return self;
239}
240
245static void setLabelFormat(Slider *self, const char *labelFormat) {
246
247 if (self->labelFormat) {
248 free(self->labelFormat);
249 }
250
251 self->labelFormat = strdup(labelFormat);
252 $(self, formatLabel);
253}
254
259static void setValue(Slider *self, double value) {
260
261 value = clamp(value, self->min, self->max);
262
263 const double delta = fabs(self->value - value);
264 if (delta > __DBL_EPSILON__) {
265 self->value = value;
266 self->control.view.needsLayout = true;
267
268 $(self, formatLabel);
269 }
270}
271
272#pragma mark - Class lifecycle
273
277static void initialize(Class *clazz) {
278
279 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
280
281 ((ViewInterface *) clazz->interface)->awakeWithDictionary = awakeWithDictionary;
282 ((ViewInterface *) clazz->interface)->init = init;
283 ((ViewInterface *) clazz->interface)->layoutSubviews = layoutSubviews;
284 ((ViewInterface *) clazz->interface)->render = render;
285
286 ((ControlInterface *) clazz->interface)->captureEvent = captureEvent;
287
288 ((SliderInterface *) clazz->interface)->formatLabel = formatLabel;
289 ((SliderInterface *) clazz->interface)->initWithFrame = initWithFrame;
290 ((SliderInterface *) clazz->interface)->setValue = setValue;
291 ((SliderInterface *) clazz->interface)->setLabelFormat = setLabelFormat;
292}
293
298Class *_Slider(void) {
299 static Class *clazz;
300 static Once once;
301
302 do_once(&once, {
303 clazz = _initialize(&(const ClassDef) {
304 .name = "Slider",
305 .superclass = _Control(),
306 .instanceSize = sizeof(Slider),
307 .interfaceOffset = offsetof(Slider, interface),
308 .interfaceSize = sizeof(SliderInterface),
310 });
311 });
312
313 return clazz;
314}
315
316#undef _Class
@ ControlStateHighlighted
Definition: Control.h:68
View logging facilities via SDL_Log.
#define MVC_LogWarn(fmt,...)
Definition: Log.h:52
static void dealloc(Object *self)
Definition: Slider.c:37
static void initialize(Class *clazz)
Definition: Slider.c:277
A Control allowing users to drag a handle to select a numeric value.
@ InletTypeDouble
Definition: View+JSON.h:67
@ InletTypeCharacters
Definition: View+JSON.h:52
@ 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
Box * initWithFrame(Box *self, const SDL_Rect *frame)
Initializes this Box with the given frame.
Definition: Box.c:92
CollectionView * init(CollectionView *self, const SDL_Rect *frame)
Initializes this CollectionView with the specified frame and style.
Controls are Views which capture events and dispatch Actions.
Definition: Control.h:83
_Bool captureEvent(Control *self, const SDL_Event *event)
Captures a given event, potentially altering the state of this Control.
Definition: Button.c:76
Class * _Control(void)
The Control archetype.
Definition: Control.c:379
unsigned int state
The bit mask of ControlState.
Definition: Control.h:110
View view
The superclass.
Definition: Control.h:88
void sizeCharacters(const Font *self, const char *chars, int *w, int *h)
Definition: Font.c:272
Inlets enable inbound data binding of View attributes through JSON.
Definition: View+JSON.h:155
Label * initWithText(Label *self, const char *text, Font *font)
Initializes this Label with the given text and Font.
Definition: Label.c:96
void setLabelFormat(ProgressBar *self, const char *labelFormat)
Changes this ProgressBar's label format and calls appropriate update functions.
Definition: ProgressBar.c:145
void formatLabel(ProgressBar *self)
Forces an update on this ProgressBar's label.
Definition: ProgressBar.c:90
void setValue(const ProgressBar *self, double value)
Sets the value, which is clamped to min and max.
The Renderer is responsible for rasterizing the View hierarchy of a WindowController.
Definition: Renderer.h:50
void drawLine(const Renderer *self, const SDL_Point *points)
Draws a line segment between two points using GL_LINE_STRIP.
Definition: Renderer.c:94
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
Class * _Slider(void)
The Slider archetype.
Definition: Slider.c:298
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
Control control
The superclass.
Definition: Slider.h:67
Text rendered with TrueType fonts.
Definition: Text.h:41
View view
The superclass.
Definition: Text.h:46
void setText(Text *self, const char *text)
Sets this Text's text.
Definition: Text.c:261
Font * font
The Font.
Definition: Text.h:64
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
Definition: View.h:133
SDL_Rect bounds(const View *self)
Definition: View.c:394
_Bool bind(View *self, const Inlet *inlets, const Dictionary *dictionary)
Performs data binding for the Inlets described in dictionary.
_Bool needsLayout
If true, this View will layout its subviews before it is drawn.
Definition: View.h:221
void addSubview(View *self, View *subview)
Adds a subview to this view, to be drawn above its siblings.
Definition: PageView.c:35
void addClassName(View *self, const char *className)
Adds the given class name to this View.
Definition: View.c:120
_Bool didReceiveEvent(const View *self, const SDL_Event *event)
Definition: View.c:546
ViewPadding padding
The padding.
Definition: View.h:233
void awakeWithDictionary(View *self, const Dictionary *dictionary)
Wakes this View with the specified Dictionary.
Definition: Box.c:50
SDL_Rect renderFrame(const View *self)
Definition: View.c:1275
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:190
void render(View *self, Renderer *renderer)
Renders this View using the given renderer.
Definition: Control.c:131
int left
Definition: View.h:100