1 /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * This file is part of openfx-supportext <https://github.com/devernay/openfx-supportext>,
4 * Copyright (C) 2013-2018 INRIA
5 *
6 * openfx-supportext is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * openfx-supportext is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with openfx-supportext. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
18 * ***** END LICENSE BLOCK ***** */
19
20 /*
21 * OFX generic position interact.
22 */
23
24 #ifndef openfx_supportext_ofxsPositionInteract_h
25 #define openfx_supportext_ofxsPositionInteract_h
26
27 #include <cmath>
28
29 #ifdef __APPLE__
30 #include <OpenGL/gl.h>
31 #else
32 #include <GL/gl.h>
33 #endif
34
35 #include <ofxsInteract.h>
36 #include <ofxsImageEffect.h>
37 #include "ofxsOGLTextRenderer.h"
38 #include "ofxsMacros.h"
39
40 namespace OFX {
41 /// template for a generic position interact.
42 /*
43 The PositionInteractParam class must define a static name() function, returning the OFX parameter name.
44 (using const char* directly as template parameter is not reliable) :
45 namespace {
46 struct MyPositionInteractParam {
47 static const char *name() { return kMyName; }
48 };
49 }
50
51 // the describe() function should include the declaration of the interact:
52 desc.setOverlayInteractDescriptor(new PositionOverlayDescriptor<MyPositionInteractParam>);
53
54 // The position param should be defined is describeInContext() as follows:
55 Double2DParamDescriptor* position = desc.defineDouble2DParam(kMyName);
56 position->setLabel(kMyLabel, kMyLabel, kMyLabel);
57 position->setHint(kMyHint);
58 position->setDoubleType(eDoubleTypeXYAbsolute);
59 position->setDefaultCoordinateSystem(eCoordinatesNormalised);
60 position->setDefault(0.5, 0.5);
61 if (page) {
62 page->addChild(*position);
63 }
64 */
65 template<typename PositionInteractParam>
66 class PositionInteract
67 : public OFX::OverlayInteract
68 {
69 public:
PositionInteract(OfxInteractHandle handle,OFX::ImageEffect * effect)70 PositionInteract(OfxInteractHandle handle,
71 OFX::ImageEffect* effect)
72 : OFX::OverlayInteract(handle)
73 , _state(eMouseStateInactive)
74 , _position(NULL)
75 , _interactive(NULL)
76 , _interactiveDrag(false)
77 , _hasNativeHostPositionHandle(false)
78 {
79 _position = effect->fetchDouble2DParam( PositionInteractParam::name() );
80 if ( PositionInteractParam::interactiveName() ) {
81 _interactive = effect->fetchBooleanParam( PositionInteractParam::interactiveName() );
82 }
83 assert(_position);
84 _hasNativeHostPositionHandle = _position->getHostHasNativeOverlayHandle();
85 _penPosition.x = _penPosition.y = 0;
86 }
87
88 private:
89 // overridden functions from OFX::Interact to do things
90 virtual bool draw(const OFX::DrawArgs &args) OVERRIDE FINAL;
91 virtual bool penMotion(const OFX::PenArgs &args) OVERRIDE FINAL;
92 virtual bool penDown(const OFX::PenArgs &args) OVERRIDE FINAL;
93 virtual bool penUp(const OFX::PenArgs &args) OVERRIDE FINAL;
94 virtual void loseFocus(const FocusArgs &args) OVERRIDE FINAL;
95
96 private:
97 enum MouseStateEnum
98 {
99 eMouseStateInactive,
100 eMouseStatePoised,
101 eMouseStatePicked
102 };
103
104 MouseStateEnum _state;
105 OFX::Double2DParam* _position;
106 OFX::BooleanParam* _interactive;
107 OfxPointD _penPosition;
108 bool _interactiveDrag;
109 bool _hasNativeHostPositionHandle;
110
pointSize()111 double pointSize() const
112 {
113 return 5;
114 }
115
pointTolerance()116 double pointTolerance() const
117 {
118 return 6;
119 }
120
121 // round to the closest int, 1/10 int, etc
122 // this make parameter editing easier
123 // pscale is args.pixelScale.x / args.renderScale.x;
124 // pscale10 is the power of 10 below pscale
fround(double val,double pscale)125 inline double fround(double val,
126 double pscale)
127 {
128 double pscale10 = std::pow( 10., std::floor( std::log10(pscale) ) );
129
130 return pscale10 * std::floor(val / pscale10 + 0.5);
131 }
132 };
133
134 template <typename ParamName>
135 bool
draw(const OFX::DrawArgs & args)136 PositionInteract<ParamName>::draw(const OFX::DrawArgs &args)
137 {
138 if (_hasNativeHostPositionHandle) {
139 return false;
140 }
141 if ( _position->getIsSecret() ||
142 !_position->getIsEnable() ) {
143 return false;
144 }
145
146 OfxRGBColourD color = { 0.8, 0.8, 0.8 };
147 getSuggestedColour(color);
148 //const OfxPointD& pscale = args.pixelScale;
149 GLdouble projection[16];
150 glGetDoublev( GL_PROJECTION_MATRIX, projection);
151 GLint viewport[4];
152 glGetIntegerv(GL_VIEWPORT, viewport);
153 OfxPointD shadow; // how much to translate GL_PROJECTION to get exactly one pixel on screen
154 shadow.x = 2. / (projection[0] * viewport[2]);
155 shadow.y = 2. / (projection[5] * viewport[3]);
156
157 OfxRGBColourF col;
158 switch (_state) {
159 case eMouseStateInactive:
160 col.r = (float)color.r; col.g = (float)color.g; col.b = (float)color.b; break;
161 case eMouseStatePoised:
162 col.r = 0.f; col.g = 1.0f; col.b = 0.0f; break;
163 case eMouseStatePicked:
164 col.r = 0.f; col.g = 1.0f; col.b = 0.0f; break;
165 }
166
167 OfxPointD pos;
168 if (_state == eMouseStatePicked) {
169 pos = _penPosition;
170 } else {
171 _position->getValueAtTime(args.time, pos.x, pos.y);
172 }
173 //glPushAttrib(GL_ALL_ATTRIB_BITS); // caller is responsible for protecting attribs
174 glPointSize( (float)pointSize() );
175
176 // Draw everything twice
177 // l = 0: shadow
178 // l = 1: drawing
179 for (int l = 0; l < 2; ++l) {
180 // shadow (uses GL_PROJECTION)
181 glMatrixMode(GL_PROJECTION);
182 int direction = (l == 0) ? 1 : -1;
183 // translate (1,-1) pixels
184 glTranslated(direction * shadow.x, -direction * shadow.y, 0);
185 glMatrixMode(GL_MODELVIEW); // Modelview should be used on Nuke
186
187 glColor3f(col.r * l, col.g * l, col.b * l);
188 glBegin(GL_POINTS);
189 glVertex2d(pos.x, pos.y);
190 glEnd();
191 OFX::TextRenderer::bitmapString( pos.x, pos.y, ParamName::name() );
192 }
193
194 //glPopAttrib();
195
196 return true;
197 } // draw
198
199 // overridden functions from OFX::Interact to do things
200 template <typename ParamName>
201 bool
penMotion(const OFX::PenArgs & args)202 PositionInteract<ParamName>::penMotion(const OFX::PenArgs &args)
203 {
204 if (_hasNativeHostPositionHandle) {
205 return false;
206 }
207 if ( _position->getIsSecret() ||
208 !_position->getIsEnable() ) {
209 return false;
210 }
211
212 const OfxPointD& pscale = args.pixelScale;
213 OfxPointD pos;
214 if (_state == eMouseStatePicked) {
215 pos = _penPosition;
216 } else {
217 _position->getValueAtTime(args.time, pos.x, pos.y);
218 }
219
220 // pen position is in cannonical coords
221 const OfxPointD &penPos = args.penPosition;
222 bool didSomething = false;
223 bool valuesChanged = false;
224
225 switch (_state) {
226 case eMouseStateInactive:
227 case eMouseStatePoised: {
228 // are we in the box, become 'poised'
229 MouseStateEnum newState;
230 if ( ( std::fabs(penPos.x - pos.x) <= pointTolerance() * pscale.x) &&
231 ( std::fabs(penPos.y - pos.y) <= pointTolerance() * pscale.y) ) {
232 newState = eMouseStatePoised;
233 } else {
234 newState = eMouseStateInactive;
235 }
236
237 if (_state != newState) {
238 _state = newState;
239 requestRedraw();
240 }
241 didSomething = (_state == eMouseStatePoised);
242 break;
243 }
244
245 case eMouseStatePicked: {
246 _penPosition = args.penPosition;
247 valuesChanged = true;
248 break;
249 }
250 }
251
252 if ( (_state != eMouseStateInactive) && _interactiveDrag && valuesChanged ) {
253 _position->setValue( fround(_penPosition.x, pscale.x), fround(_penPosition.y, pscale.y) );
254 }
255
256 if (valuesChanged) {
257 requestRedraw();
258 }
259
260 return didSomething || valuesChanged;
261 } // >::penMotion
262
263 template <typename ParamName>
264 bool
penDown(const OFX::PenArgs & args)265 PositionInteract<ParamName>::penDown(const OFX::PenArgs &args)
266 {
267 if (_hasNativeHostPositionHandle) {
268 return false;
269 }
270 if (!_position) {
271 return false;
272 }
273 if ( _position->getIsSecret() ||
274 !_position->getIsEnable() ) {
275 return false;
276 }
277
278 bool didSomething = false;
279 penMotion(args);
280 if (_state == eMouseStatePoised) {
281 _state = eMouseStatePicked;
282 _penPosition = args.penPosition;
283 if (_interactive) {
284 _interactive->getValueAtTime(args.time, _interactiveDrag);
285 }
286 didSomething = true;
287 }
288
289 return didSomething;
290 }
291
292 template <typename ParamName>
293 bool
penUp(const OFX::PenArgs & args)294 PositionInteract<ParamName>::penUp(const OFX::PenArgs &args)
295 {
296 if (_hasNativeHostPositionHandle) {
297 return false;
298 }
299 if (!_position) {
300 return false;
301 }
302 if ( _position->getIsSecret() ||
303 !_position->getIsEnable() ) {
304 return false;
305 }
306
307 bool didSomething = false;
308 if (_state == eMouseStatePicked) {
309 if (!_interactiveDrag) {
310 const OfxPointD& pscale = args.pixelScale;
311 _position->setValue( fround(_penPosition.x, pscale.x), fround(_penPosition.y, pscale.y) );
312 }
313 penMotion(args);
314 _state = eMouseStateInactive;
315 didSomething = true;
316 }
317
318 if (didSomething) {
319 requestRedraw();
320 }
321
322 return didSomething;
323 }
324
325 /** @brief Called when the interact is loses input focus */
326 template <typename ParamName>
327 void
loseFocus(const OFX::FocusArgs &)328 PositionInteract<ParamName>::loseFocus(const OFX::FocusArgs & /*args*/)
329 {
330 _interactiveDrag = false;
331 if (_state != eMouseStateInactive) {
332 _state = eMouseStateInactive;
333 requestRedraw();
334 }
335 }
336
337 template <typename ParamName>
338 class PositionOverlayDescriptor
339 : public OFX::DefaultEffectOverlayDescriptor<PositionOverlayDescriptor<ParamName>, PositionInteract<ParamName> >
340 {
341 };
342 } // namespace OFX
343
344 #endif /* defined(openfx_supportext_ofxsPositionInteract_h) */
345