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 rectangle interact with 4 corner points + center point and 4 mid-points.
22  * You can use it to define any rectangle in an image resizable by the user.
23  */
24 
25 #include "ofxsRectangleInteract.h"
26 #include <cmath>
27 
28 #ifdef __APPLE__
29 #include <OpenGL/gl.h>
30 #else
31 #include <GL/gl.h>
32 #endif
33 
34 #define POINT_SIZE 5
35 #define POINT_TOLERANCE 6
36 #define CROSS_SIZE 7
37 #define HANDLE_SIZE 6
38 
39 using namespace OFX;
40 
41 using OFX::RectangleInteract;
42 
43 static bool
isNearby(const OfxPointD & p,double x,double y,double tolerance,const OfxPointD & pscale)44 isNearby(const OfxPointD & p,
45          double x,
46          double y,
47          double tolerance,
48          const OfxPointD & pscale)
49 {
50     return std::fabs(p.x - x) <= tolerance * pscale.x &&  std::fabs(p.y - y) <= tolerance * pscale.y;
51 }
52 
53 // round to the closest int, 1/10 int, etc
54 // this make parameter editing easier
55 // pscale is args.pixelScale.x / args.renderScale.x;
56 // pscale10 is the power of 10 below pscale
57 static double
fround(double val,double pscale)58 fround(double val,
59        double pscale)
60 {
61     if (pscale == 0) {
62         return val;
63     }
64     double pscale10 = std::pow( 10., std::floor( std::log10(pscale) ) );
65 
66     return pscale10 * std::floor(val / pscale10 + 0.5);
67 }
68 
69 static void
drawPoint(const OfxRGBColourD & color,bool draw,double x,double y,RectangleInteract::DrawStateEnum id,RectangleInteract::DrawStateEnum ds,bool keepAR,int l)70 drawPoint(const OfxRGBColourD &color,
71           bool draw,
72           double x,
73           double y,
74           RectangleInteract::DrawStateEnum id,
75           RectangleInteract::DrawStateEnum ds,
76           bool keepAR,
77           int l)
78 {
79     if (draw) {
80         if (ds == id) {
81             if (keepAR) {
82                 glColor3f(1.f * l, 0.f * l, 0.f * l);
83             } else {
84                 glColor3f(0.f * l, 1.f * l, 0.f * l);
85             }
86         } else {
87             glColor3f( (float)color.r * l, (float)color.g * l, (float)color.b * l );
88         }
89         glVertex2d(x, y);
90     }
91 }
92 
93 bool
draw(const DrawArgs & args)94 RectangleInteract::draw(const DrawArgs &args)
95 {
96     if ( _btmLeft->getIsSecret() || _size->getIsSecret() ||
97          !_btmLeft->getIsEnable() || !_size->getIsEnable() ||
98          ( _enable && !_enable->getValueAtTime(args.time) ) ) {
99         return false;
100     }
101 
102     OfxRGBColourD color = { 0.8, 0.8, 0.8 };
103     getSuggestedColour(color);
104     const OfxPointD& pscale = args.pixelScale;
105     GLdouble projection[16];
106     glGetDoublev( GL_PROJECTION_MATRIX, projection);
107     GLint viewport[4];
108     glGetIntegerv(GL_VIEWPORT, viewport);
109     OfxPointD shadow; // how much to translate GL_PROJECTION to get exactly one pixel on screen
110     shadow.x = 2. / (projection[0] * viewport[2]);
111     shadow.y = 2. / (projection[5] * viewport[3]);
112 
113     double x1, y1, w, h;
114     if (_mouseState != eMouseStateIdle) {
115         x1 = _btmLeftDragPos.x;
116         y1 = _btmLeftDragPos.y;
117         w = _sizeDrag.x;
118         h = _sizeDrag.y;
119     } else {
120         _btmLeft->getValueAtTime(args.time, x1, y1);
121         _size->getValueAtTime(args.time, w, h);
122     }
123     double x2 = x1 + w;
124     double y2 = y1 + h;
125     double xc = x1 + w / 2;
126     double yc = y1 + h / 2;
127     const bool keepAR = _modifierStateShift > 0;
128     const bool centered = _modifierStateCtrl > 0;
129 
130     //glPushAttrib(GL_ALL_ATTRIB_BITS); // caller is responsible for protecting attribs
131     aboutToCheckInteractivity(args.time);
132 
133     glDisable(GL_LINE_STIPPLE);
134     glEnable(GL_LINE_SMOOTH);
135     glDisable(GL_POINT_SMOOTH);
136     glEnable(GL_BLEND);
137     glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
138     glLineWidth(1.5f);
139     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
140 
141     // Draw everything twice
142     // l = 0: shadow
143     // l = 1: drawing
144     for (int l = 0; l < 2; ++l) {
145         // shadow (uses GL_PROJECTION)
146         glMatrixMode(GL_PROJECTION);
147         int direction = (l == 0) ? 1 : -1;
148         // translate (1,-1) pixels
149         glTranslated(direction * shadow.x, -direction * shadow.y, 0);
150         glMatrixMode(GL_MODELVIEW); // Modelview should be used on Nuke
151 
152         glColor3f( (float)color.r * l, (float)color.g * l, (float)color.b * l );
153 
154         glBegin(GL_LINE_LOOP);
155         glVertex2d(x1, y1);
156         glVertex2d(x1, y2);
157         glVertex2d(x2, y2);
158         glVertex2d(x2, y1);
159         glEnd();
160 
161         glPointSize(POINT_SIZE);
162         glBegin(GL_POINTS);
163         drawPoint(color, allowBtmLeftInteraction(),  x1, y1, eDrawStateHoveringBtmLeft,  _drawState, keepAR, l);
164         drawPoint(color, allowMidLeftInteraction(),  x1, yc, eDrawStateHoveringMidLeft,  _drawState, false,  l);
165         drawPoint(color, allowTopLeftInteraction(),  x1, y2, eDrawStateHoveringTopLeft,  _drawState, keepAR, l);
166         drawPoint(color, allowBtmMidInteraction(),   xc, y1, eDrawStateHoveringBtmMid,   _drawState, false,  l);
167         drawPoint(color, allowCenterInteraction(),   xc, yc, eDrawStateHoveringCenter,   _drawState, false,  l);
168         drawPoint(color, allowTopMidInteraction(),   xc, y2, eDrawStateHoveringTopMid,   _drawState, false,  l);
169         drawPoint(color, allowBtmRightInteraction(), x2, y1, eDrawStateHoveringBtmRight, _drawState, keepAR, l);
170         drawPoint(color, allowMidRightInteraction(), x2, yc, eDrawStateHoveringMidRight, _drawState, false,  l);
171         drawPoint(color, allowTopRightInteraction(), x2, y2, eDrawStateHoveringTopRight, _drawState, keepAR, l);
172         glEnd();
173         glPointSize(1);
174 
175         ///draw center cross hair
176         glBegin(GL_LINES);
177         if ( (_drawState == eDrawStateHoveringCenter) || ( centered && (_drawState != eDrawStateInactive) ) ) {
178             glColor3f(0.f * l, 1.f * l, 0.f * l);
179         } else if ( !allowCenterInteraction() ) {
180             glColor3f( (float)(color.r / 2) * l, (float)(color.g / 2) * l, (float)(color.b / 2) * l );
181         } else {
182             glColor3f( (float)color.r * l, (float)color.g * l, (float)color.b * l );
183         }
184         glVertex2d(xc - CROSS_SIZE * pscale.x, yc);
185         glVertex2d(xc + CROSS_SIZE * pscale.x, yc);
186         glVertex2d(xc, yc - CROSS_SIZE * pscale.y);
187         glVertex2d(xc, yc + CROSS_SIZE * pscale.y);
188         glEnd();
189     }
190     //glPopAttrib();
191 
192     return true;
193 } // draw
194 
195 bool
penMotion(const PenArgs & args)196 RectangleInteract::penMotion(const PenArgs &args)
197 {
198     if ( _btmLeft->getIsSecret() || _size->getIsSecret() ||
199          !_btmLeft->getIsEnable() || !_size->getIsEnable() ||
200          ( _enable && !_enable->getValueAtTime(args.time) ) ) {
201         return false;
202     }
203 
204     const OfxPointD& pscale = args.pixelScale;
205     double x1, y1, w, h;
206     if (_mouseState != eMouseStateIdle) {
207         x1 = _btmLeftDragPos.x;
208         y1 = _btmLeftDragPos.y;
209         w = _sizeDrag.x;
210         h = _sizeDrag.y;
211     } else {
212         _btmLeft->getValueAtTime(args.time, x1, y1);
213         _size->getValueAtTime(args.time, w, h);
214     }
215     double x2 = x1 + w;
216     double y2 = y1 + h;
217     double xc = x1 + w / 2;
218     double yc = y1 + h / 2;
219     bool didSomething = false;
220     bool valuesChanged = false;
221     OfxPointD delta;
222     delta.x = args.penPosition.x - _lastMousePos.x;
223     delta.y = args.penPosition.y - _lastMousePos.y;
224 
225     bool lastStateWasHovered = _drawState != eDrawStateInactive;
226 
227 
228     aboutToCheckInteractivity(args.time);
229     // test center first
230     if ( isNearby(args.penPosition, xc, yc, POINT_TOLERANCE, pscale)  && allowCenterInteraction() ) {
231         _drawState = eDrawStateHoveringCenter;
232         didSomething = true;
233     } else if ( isNearby(args.penPosition, x1, y1, POINT_TOLERANCE, pscale) && allowBtmLeftInteraction() ) {
234         _drawState = eDrawStateHoveringBtmLeft;
235         didSomething = true;
236     } else if ( isNearby(args.penPosition, x2, y1, POINT_TOLERANCE, pscale) && allowBtmRightInteraction() ) {
237         _drawState = eDrawStateHoveringBtmRight;
238         didSomething = true;
239     } else if ( isNearby(args.penPosition, x1, y2, POINT_TOLERANCE, pscale)  && allowTopLeftInteraction() ) {
240         _drawState = eDrawStateHoveringTopLeft;
241         didSomething = true;
242     } else if ( isNearby(args.penPosition, x2, y2, POINT_TOLERANCE, pscale) && allowTopRightInteraction() ) {
243         _drawState = eDrawStateHoveringTopRight;
244         didSomething = true;
245     } else if ( isNearby(args.penPosition, xc, y1, POINT_TOLERANCE, pscale)  && allowBtmMidInteraction() ) {
246         _drawState = eDrawStateHoveringBtmMid;
247         didSomething = true;
248     } else if ( isNearby(args.penPosition, xc, y2, POINT_TOLERANCE, pscale) && allowTopMidInteraction() ) {
249         _drawState = eDrawStateHoveringTopMid;
250         didSomething = true;
251     } else if ( isNearby(args.penPosition, x1, yc, POINT_TOLERANCE, pscale)  && allowMidLeftInteraction() ) {
252         _drawState = eDrawStateHoveringMidLeft;
253         didSomething = true;
254     } else if ( isNearby(args.penPosition, x2, yc, POINT_TOLERANCE, pscale) && allowMidRightInteraction() ) {
255         _drawState = eDrawStateHoveringMidRight;
256         didSomething = true;
257     } else {
258         _drawState = eDrawStateInactive;
259     }
260 
261     const bool keepAR = _modifierStateShift > 0;
262     const bool centered = _modifierStateCtrl > 0;
263     if ( keepAR && (_sizeDrag.x > 0.) && (_sizeDrag.y > 0.) &&
264          ( ( _mouseState == eMouseStateDraggingTopLeft) ||
265            ( _mouseState == eMouseStateDraggingTopRight) ||
266            ( _mouseState == eMouseStateDraggingBtmLeft) ||
267            ( _mouseState == eMouseStateDraggingBtmRight) ) ) {
268         double r2 = _sizeDrag.x * _sizeDrag.x + _sizeDrag.y * _sizeDrag.y;
269         if ( (_mouseState == eMouseStateDraggingTopRight) ||
270              ( _mouseState == eMouseStateDraggingBtmLeft) ) {
271             double dotprod = (delta.x * _sizeDrag.y + delta.y * _sizeDrag.x) / r2;
272             delta.x = _sizeDrag.x * dotprod;
273             delta.y = _sizeDrag.y * dotprod;
274         } else {
275             double dotprod = (delta.x * _sizeDrag.y - delta.y * _sizeDrag.x) / r2;
276             delta.x = _sizeDrag.x * dotprod;
277             delta.y = -_sizeDrag.y * dotprod;
278         }
279     }
280     if (_mouseState == eMouseStateDraggingBtmLeft) {
281         _drawState = eDrawStateHoveringBtmLeft;
282         OfxPointD topRight;
283         topRight.x = _btmLeftDragPos.x + _sizeDrag.x;
284         topRight.y = _btmLeftDragPos.y + _sizeDrag.y;
285         _btmLeftDragPos.x += delta.x;
286         _btmLeftDragPos.y += delta.y;
287         _sizeDrag.x = topRight.x - _btmLeftDragPos.x;
288         _sizeDrag.y = topRight.y - _btmLeftDragPos.y;
289         if (centered) {
290             _sizeDrag.x -= delta.x;
291             _sizeDrag.y -= delta.y;
292         }
293         valuesChanged = true;
294     } else if (_mouseState == eMouseStateDraggingTopLeft) {
295         _drawState = eDrawStateHoveringTopLeft;
296         OfxPointD btmRight;
297         btmRight.x = _btmLeftDragPos.x + _sizeDrag.x;
298         btmRight.y = _btmLeftDragPos.y;
299         _btmLeftDragPos.x += delta.x;
300         _sizeDrag.y += delta.y;
301         _sizeDrag.x = btmRight.x - _btmLeftDragPos.x;
302         if (centered) {
303             _sizeDrag.x -= delta.x;
304             _sizeDrag.y += delta.y;
305             _btmLeftDragPos.y -= delta.y;
306         }
307         valuesChanged = true;
308     } else if (_mouseState == eMouseStateDraggingTopRight) {
309         _drawState = eDrawStateHoveringTopRight;
310         _sizeDrag.x += delta.x;
311         _sizeDrag.y += delta.y;
312         if (centered) {
313             _sizeDrag.x += delta.x;
314             _btmLeftDragPos.x -= delta.x;
315             _sizeDrag.y += delta.y;
316             _btmLeftDragPos.y -= delta.y;
317         }
318         valuesChanged = true;
319     } else if (_mouseState == eMouseStateDraggingBtmRight) {
320         _drawState = eDrawStateHoveringBtmRight;
321         OfxPointD topLeft;
322         topLeft.x = _btmLeftDragPos.x;
323         topLeft.y = _btmLeftDragPos.y + _sizeDrag.y;
324         _sizeDrag.x += delta.x;
325         _btmLeftDragPos.y += delta.y;
326         _sizeDrag.y = topLeft.y - _btmLeftDragPos.y;
327         if (centered) {
328             _sizeDrag.x += delta.x;
329             _btmLeftDragPos.x -= delta.x;
330             _sizeDrag.y -= delta.y;
331         }
332         valuesChanged = true;
333     } else if (_mouseState == eMouseStateDraggingTopMid) {
334         _drawState = eDrawStateHoveringTopMid;
335         _sizeDrag.y += delta.y;
336         if (centered) {
337             _sizeDrag.y += delta.y;
338             _btmLeftDragPos.y -= delta.y;
339         }
340         valuesChanged = true;
341     } else if (_mouseState == eMouseStateDraggingMidRight) {
342         _drawState = eDrawStateHoveringMidRight;
343         _sizeDrag.x += delta.x;
344         if (centered) {
345             _sizeDrag.x += delta.x;
346             _btmLeftDragPos.x -= delta.x;
347         }
348         valuesChanged = true;
349     } else if (_mouseState == eMouseStateDraggingBtmMid) {
350         _drawState = eDrawStateHoveringBtmMid;
351         double top = _btmLeftDragPos.y + _sizeDrag.y;
352         _btmLeftDragPos.y += delta.y;
353         _sizeDrag.y = top - _btmLeftDragPos.y;
354         if (centered) {
355             _sizeDrag.y -= delta.y;
356         }
357         valuesChanged = true;
358     } else if (_mouseState == eMouseStateDraggingMidLeft) {
359         _drawState = eDrawStateHoveringMidLeft;
360         double right = _btmLeftDragPos.x + _sizeDrag.x;
361         _btmLeftDragPos.x += delta.x;
362         _sizeDrag.x = right - _btmLeftDragPos.x;
363         if (centered) {
364             _sizeDrag.x -= delta.x;
365         }
366         valuesChanged = true;
367     } else if (_mouseState == eMouseStateDraggingCenter) {
368         _drawState = eDrawStateHoveringCenter;
369         _btmLeftDragPos.x += delta.x;
370         _btmLeftDragPos.y += delta.y;
371         valuesChanged = true;
372     }
373 
374 
375     //if size is negative shift bottom left
376     if (_sizeDrag.x < 0) {
377         if (_mouseState == eMouseStateDraggingBtmLeft) {
378             _mouseState = eMouseStateDraggingBtmRight;
379         } else if (_mouseState == eMouseStateDraggingMidLeft) {
380             _mouseState = eMouseStateDraggingMidRight;
381         } else if (_mouseState == eMouseStateDraggingTopLeft) {
382             _mouseState = eMouseStateDraggingTopRight;
383         } else if (_mouseState == eMouseStateDraggingBtmRight) {
384             _mouseState = eMouseStateDraggingBtmLeft;
385         } else if (_mouseState == eMouseStateDraggingMidRight) {
386             _mouseState = eMouseStateDraggingMidLeft;
387         } else if (_mouseState == eMouseStateDraggingTopRight) {
388             _mouseState = eMouseStateDraggingTopLeft;
389         }
390 
391         _btmLeftDragPos.x += _sizeDrag.x;
392         _sizeDrag.x = -_sizeDrag.x;
393         valuesChanged = true;
394     }
395     if (_sizeDrag.y < 0) {
396         if (_mouseState == eMouseStateDraggingTopLeft) {
397             _mouseState = eMouseStateDraggingBtmLeft;
398         } else if (_mouseState == eMouseStateDraggingTopMid) {
399             _mouseState = eMouseStateDraggingBtmMid;
400         } else if (_mouseState == eMouseStateDraggingTopRight) {
401             _mouseState = eMouseStateDraggingBtmRight;
402         } else if (_mouseState == eMouseStateDraggingBtmLeft) {
403             _mouseState = eMouseStateDraggingTopLeft;
404         } else if (_mouseState == eMouseStateDraggingBtmMid) {
405             _mouseState = eMouseStateDraggingTopMid;
406         } else if (_mouseState == eMouseStateDraggingBtmRight) {
407             _mouseState = eMouseStateDraggingTopRight;
408         }
409 
410         _btmLeftDragPos.y += _sizeDrag.y;
411         _sizeDrag.y = -_sizeDrag.y;
412         valuesChanged = true;
413     }
414 
415     ///forbid 0 pixels wide crop rectangles
416     if (_sizeDrag.x < 1) {
417         _sizeDrag.x = 1;
418         valuesChanged = true;
419     }
420     if (_sizeDrag.y < 1) {
421         _sizeDrag.y = 1;
422         valuesChanged = true;
423     }
424 
425     ///repaint if we toggled off a hovered handle
426     if (lastStateWasHovered) {
427         didSomething = true;
428     }
429 
430     if ( (_mouseState != eMouseStateIdle) && _interactiveDrag && valuesChanged ) {
431         setValue(_btmLeftDragPos, _sizeDrag, args.pixelScale);
432         // no need to redraw overlay since it is slave to the paramaters
433     } else if (didSomething || valuesChanged) {
434         requestRedraw();
435     }
436 
437 
438     _lastMousePos = args.penPosition;
439 
440     return didSomething || valuesChanged;
441 } // penMotion
442 
443 bool
penDown(const PenArgs & args)444 RectangleInteract::penDown(const PenArgs &args)
445 {
446     if ( _btmLeft->getIsSecret() || _size->getIsSecret() ||
447          !_btmLeft->getIsEnable() || !_size->getIsEnable() ||
448          ( _enable && !_enable->getValueAtTime(args.time) ) ) {
449         return false;
450     }
451 
452     const OfxPointD& pscale = args.pixelScale;
453     double x1, y1, w, h;
454     if (_mouseState != eMouseStateIdle) {
455         x1 = _btmLeftDragPos.x;
456         y1 = _btmLeftDragPos.y;
457         w = _sizeDrag.x;
458         h = _sizeDrag.y;
459     } else {
460         _btmLeft->getValueAtTime(args.time, x1, y1);
461         _size->getValueAtTime(args.time, w, h);
462         if ( _interactive && _interactive->getIsEnable() ) {
463             _interactive->getValueAtTime(args.time, _interactiveDrag);
464         } else {
465             _interactiveDrag = false;
466         }
467     }
468     double x2 = x1 + w;
469     double y2 = y1 + h;
470     double xc = x1 + w / 2;
471     double yc = y1 + h / 2;
472     bool didSomething = false;
473 
474     aboutToCheckInteractivity(args.time);
475 
476     // test center first
477     if ( isNearby(args.penPosition, xc, yc, POINT_TOLERANCE, pscale)  && allowCenterInteraction() ) {
478         _mouseState = eMouseStateDraggingCenter;
479         didSomething = true;
480     } else if ( isNearby(args.penPosition, x1, y1, POINT_TOLERANCE, pscale) && allowBtmLeftInteraction() ) {
481         _mouseState = eMouseStateDraggingBtmLeft;
482         didSomething = true;
483     } else if ( isNearby(args.penPosition, x2, y1, POINT_TOLERANCE, pscale) && allowBtmRightInteraction() ) {
484         _mouseState = eMouseStateDraggingBtmRight;
485         didSomething = true;
486     } else if ( isNearby(args.penPosition, x1, y2, POINT_TOLERANCE, pscale)  && allowTopLeftInteraction() ) {
487         _mouseState = eMouseStateDraggingTopLeft;
488         didSomething = true;
489     } else if ( isNearby(args.penPosition, x2, y2, POINT_TOLERANCE, pscale) && allowTopRightInteraction() ) {
490         _mouseState = eMouseStateDraggingTopRight;
491         didSomething = true;
492     } else if ( isNearby(args.penPosition, xc, y1, POINT_TOLERANCE, pscale)  && allowBtmMidInteraction() ) {
493         _mouseState = eMouseStateDraggingBtmMid;
494         didSomething = true;
495     } else if ( isNearby(args.penPosition, xc, y2, POINT_TOLERANCE, pscale) && allowTopMidInteraction() ) {
496         _mouseState = eMouseStateDraggingTopMid;
497         didSomething = true;
498     } else if ( isNearby(args.penPosition, x1, yc, POINT_TOLERANCE, pscale)  && allowMidLeftInteraction() ) {
499         _mouseState = eMouseStateDraggingMidLeft;
500         didSomething = true;
501     } else if ( isNearby(args.penPosition, x2, yc, POINT_TOLERANCE, pscale) && allowMidRightInteraction() ) {
502         _mouseState = eMouseStateDraggingMidRight;
503         didSomething = true;
504     } else {
505         _mouseState = eMouseStateIdle;
506     }
507 
508     _btmLeftDragPos.x = x1;
509     _btmLeftDragPos.y = y1;
510     _sizeDrag.x = w;
511     _sizeDrag.y = h;
512     _lastMousePos = args.penPosition;
513     if (didSomething) {
514         requestRedraw();
515     }
516 
517     return didSomething;
518 } // penDown
519 
520 bool
penUp(const PenArgs & args)521 RectangleInteract::penUp(const PenArgs &args)
522 {
523     if ( _btmLeft->getIsSecret() || _size->getIsSecret() ||
524          !_btmLeft->getIsEnable() || !_size->getIsEnable() ||
525          ( _enable && !_enable->getValueAtTime(args.time) ) ) {
526         return false;
527     }
528 
529     bool didSmthing = false;
530 
531     if ( !_interactiveDrag && (_mouseState != eMouseStateIdle) ) {
532         // no need to redraw overlay since it is slave to the paramaters
533         setValue(_btmLeftDragPos, _sizeDrag, args.pixelScale);
534         didSmthing = true;
535     } else if (_mouseState != eMouseStateIdle) {
536         requestRedraw();
537     }
538     _mouseState = eMouseStateIdle;
539 
540     return didSmthing;
541 } // penUp
542 
543 // keyDown just updates the modifier state
544 bool
keyDown(const KeyArgs & args)545 RectangleInteract::keyDown(const KeyArgs &args)
546 {
547     if ( _btmLeft->getIsSecret() || _size->getIsSecret() ||
548          !_btmLeft->getIsEnable() || !_size->getIsEnable() ||
549          ( _enable && !_enable->getValueAtTime(args.time) ) ) {
550         return false;
551     }
552 
553     // Note that on the Mac:
554     // cmd/apple/cloverleaf is kOfxKey_Control_L
555     // ctrl is kOfxKey_Meta_L
556     // alt/option is kOfxKey_Alt_L
557     bool mustRedraw = false;
558 
559     // the two control keys may be pressed consecutively, be aware about this
560     if ( (args.keySymbol == kOfxKey_Control_L) || (args.keySymbol == kOfxKey_Control_R) ) {
561         mustRedraw = _modifierStateCtrl == 0;
562         ++_modifierStateCtrl;
563     }
564     if ( (args.keySymbol == kOfxKey_Shift_L) || (args.keySymbol == kOfxKey_Shift_R) ) {
565         mustRedraw = _modifierStateShift == 0;
566         ++_modifierStateShift;
567     }
568     if (mustRedraw) {
569         requestRedraw();
570     }
571     //std::cout << std::hex << args.keySymbol << std::endl;
572 
573     // modifiers are not "caught"
574     return false;
575 }
576 
577 // keyUp just updates the modifier state
578 bool
keyUp(const KeyArgs & args)579 RectangleInteract::keyUp(const KeyArgs &args)
580 {
581     if ( _btmLeft->getIsSecret() || _size->getIsSecret() ||
582          !_btmLeft->getIsEnable() || !_size->getIsEnable() ||
583          ( _enable && !_enable->getValueAtTime(args.time) ) ) {
584         return false;
585     }
586 
587     bool mustRedraw = false;
588 
589     if ( (args.keySymbol == kOfxKey_Control_L) || (args.keySymbol == kOfxKey_Control_R) ) {
590         // we may have missed a keypress
591         if (_modifierStateCtrl > 0) {
592             --_modifierStateCtrl;
593             mustRedraw = _modifierStateCtrl == 0;
594         }
595     }
596     if ( (args.keySymbol == kOfxKey_Shift_L) || (args.keySymbol == kOfxKey_Shift_R) ) {
597         if (_modifierStateShift > 0) {
598             --_modifierStateShift;
599             mustRedraw = _modifierStateShift == 0;
600         }
601     }
602     if (mustRedraw) {
603         requestRedraw();
604     }
605 
606     // modifiers are not "caught"
607     return false;
608 }
609 
610 /** @brief Called when the interact is loses input focus */
611 void
loseFocus(const FocusArgs &)612 RectangleInteract::loseFocus(const FocusArgs & /*args*/)
613 {
614     // reset the modifiers state
615     _modifierStateCtrl = 0;
616     _modifierStateShift = 0;
617     _interactiveDrag = false;
618 }
619 
620 OfxPointD
getBtmLeft(OfxTime time) const621 RectangleInteract::getBtmLeft(OfxTime time) const
622 {
623     OfxPointD ret;
624 
625     _btmLeft->getValueAtTime(time, ret.x, ret.y);
626 
627     return ret;
628 }
629 
630 void
setValue(OfxPointD btmLeft,OfxPointD size,const OfxPointD & pscale)631 RectangleInteract::setValue(OfxPointD btmLeft,
632                             OfxPointD size,
633                             const OfxPointD &pscale)
634 {
635     bool setBtmLeft = false;
636     bool setSize = false;
637     // round newx/y to the closest int, 1/10 int, etc
638     // this make parameter editing easier
639     switch (_mouseState) {
640     case eMouseStateIdle:
641         break;
642     case eMouseStateDraggingTopLeft:
643         btmLeft.x = fround(btmLeft.x, pscale.x);
644         size.x = fround(size.x, pscale.x);
645         size.y = fround(size.y, pscale.y);
646         setBtmLeft = setSize = true;
647         break;
648     case eMouseStateDraggingTopRight:
649         size.x = fround(size.x, pscale.x);
650         size.y = fround(size.y, pscale.y);
651         setSize = true;
652         break;
653     case eMouseStateDraggingBtmLeft:
654         btmLeft.x = fround(btmLeft.x, pscale.x);
655         btmLeft.y = fround(btmLeft.y, pscale.y);
656         size.x = fround(size.x, pscale.x);
657         size.y = fround(size.y, pscale.y);
658         setBtmLeft = setSize = true;
659         break;
660     case eMouseStateDraggingBtmRight:
661         size.x = fround(size.x, pscale.x);
662         size.y = fround(size.y, pscale.y);
663         btmLeft.y = fround(btmLeft.y, pscale.y);
664         setBtmLeft = setSize = true;
665         break;
666     case eMouseStateDraggingCenter:
667         btmLeft.x = fround(btmLeft.x, pscale.x);
668         btmLeft.y = fround(btmLeft.y, pscale.y);
669         setBtmLeft = true;
670         break;
671     case eMouseStateDraggingTopMid:
672         size.y = fround(size.y, pscale.y);
673         setSize = true;
674         break;
675     case eMouseStateDraggingMidRight:
676         size.x = fround(size.x, pscale.x);
677         setSize = true;
678         break;
679     case eMouseStateDraggingBtmMid:
680         btmLeft.y = fround(btmLeft.y, pscale.y);
681         setBtmLeft = true;
682         break;
683     case eMouseStateDraggingMidLeft:
684         btmLeft.x = fround(btmLeft.x, pscale.x);
685         setBtmLeft = true;
686         break;
687     }
688     bool editBlock =  setBtmLeft + setSize > 1;
689     if (editBlock) {
690         _effect->beginEditBlock("setRectangle");
691     }
692     _btmLeft->setValue(btmLeft.x, btmLeft.y);
693     _size->setValue(size.x, size.y);
694     if (editBlock) {
695         _effect->endEditBlock();
696     }
697 } // penDown
698