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