Merged with Tizen Branch ( builds )
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / decorator / text-decorator.cpp
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/text/decorator/text-decorator.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/integration-api/debug.h>
23 #include <dali/public-api/actors/actor.h>
24 #include <dali/public-api/adaptor-framework/timer.h>
25 #include <dali/public-api/actors/image-actor.h>
26 #include <dali/public-api/actors/layer.h>
27 #include <dali/public-api/animation/constraint.h>
28 #include <dali/public-api/common/constants.h>
29 #include <dali/public-api/common/stage.h>
30 #include <dali/public-api/events/tap-gesture.h>
31 #include <dali/public-api/events/tap-gesture-detector.h>
32 #include <dali/public-api/events/pan-gesture.h>
33 #include <dali/public-api/events/pan-gesture-detector.h>
34 #include <dali/public-api/images/resource-image.h>
35 #include <dali/public-api/math/rect.h>
36 #include <dali/public-api/math/vector2.h>
37 #include <dali/public-api/math/vector4.h>
38 #include <dali/public-api/object/property-notification.h>
39 #include <dali/public-api/signals/connection-tracker.h>
40
41 // INTERNAL INCLUDES
42 #include <dali-toolkit/public-api/controls/control.h>
43 #include <dali-toolkit/public-api/controls/control-impl.h>
44 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
45 #include <dali-toolkit/public-api/controls/default-controls/solid-color-actor.h>
46 #include <dali-toolkit/public-api/controls/text-controls/text-label.h>
47 #include <dali-toolkit/public-api/controls/text-controls/text-selection-popup.h>
48
49 #ifdef DEBUG_ENABLED
50 #define DECORATOR_DEBUG
51
52 #endif
53
54 namespace Dali
55 {
56 namespace Internal
57 {
58 namespace
59 {
60 #ifdef DECORATOR_DEBUG
61 Integration::Log::Filter* gLogFilter( Integration::Log::Filter::New(Debug::NoLogging, false, "LOG_TEXT_DECORATOR") );
62 #endif
63 }
64 }
65 }
66
67
68 // Local Data
69 namespace
70 {
71
72 const char* DEFAULT_GRAB_HANDLE_IMAGE_RELEASED( DALI_IMAGE_DIR "insertpoint-icon.png" );
73 const char* DEFAULT_GRAB_HANDLE_IMAGE_PRESSED( DALI_IMAGE_DIR "insertpoint-icon-pressed.png" );
74 const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
75 const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.png" );
76
77 const Dali::Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f );
78 const Dali::Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f );
79
80 const Dali::Vector4 LIGHT_BLUE( 0.07f, 0.41f, 0.59f, 1.0f ); // The text highlight color.
81
82 const unsigned int CURSOR_BLINK_INTERVAL = 500u; // Cursor blink interval
83 const float TO_MILLISECONDS = 1000.f;
84 const float TO_SECONDS = 1.f / TO_MILLISECONDS;
85
86 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( -0.05f );
87
88 const unsigned int SCROLL_TICK_INTERVAL = 50u;
89
90 const float SCROLL_THRESHOLD = 10.f;
91 const float SCROLL_SPEED = 300.f;
92 const float SCROLL_DISTANCE = SCROLL_SPEED * SCROLL_TICK_INTERVAL * TO_SECONDS;
93
94 /**
95  * structure to hold coordinates of each quad, which will make up the mesh.
96  */
97 struct QuadCoordinates
98 {
99   /**
100    * Default constructor
101    */
102   QuadCoordinates()
103   {
104   }
105
106   /**
107    * Constructor
108    * @param[in] x1 left co-ordinate
109    * @param[in] y1 top co-ordinate
110    * @param[in] x2 right co-ordinate
111    * @param[in] y2 bottom co-ordinate
112    */
113   QuadCoordinates(float x1, float y1, float x2, float y2)
114   : min(x1, y1),
115     max(x2, y2)
116   {
117   }
118
119   Dali::Vector2 min;                          ///< top-left (minimum) position of quad
120   Dali::Vector2 max;                          ///< bottom-right (maximum) position of quad
121 };
122
123 typedef std::vector<QuadCoordinates> QuadContainer;
124
125 /**
126  * @brief Takes a bounding rectangle in the local coordinates of an actor and returns the world coordinates Bounding Box.
127  * @param[in] boundingRectangle local bounding
128  * @param[out] Vector4 World coordinate bounding Box.
129  */
130 void LocalToWorldCoordinatesBoundingBox( const Dali::Rect<int>& boundingRectangle, Dali::Vector4& boundingBox )
131 {
132   // Convert to world coordinates and store as a Vector4 to be compatible with Property Notifications.
133   Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
134
135   const float originX = boundingRectangle.x - 0.5f * stageSize.width;
136   const float originY = boundingRectangle.y - 0.5f * stageSize.height;
137
138   boundingBox = Dali::Vector4( originX,
139                                originY,
140                                originX + boundingRectangle.width,
141                                originY + boundingRectangle.height );
142 }
143
144
145 } // end of namespace
146
147 namespace Dali
148 {
149
150 namespace Toolkit
151 {
152
153 namespace Text
154 {
155
156 struct Decorator::Impl : public ConnectionTracker
157 {
158   enum ScrollDirection
159   {
160     SCROLL_NONE,
161     SCROLL_RIGHT,
162     SCROLL_LEFT,
163     SCROLL_TOP,
164     SCROLL_BOTTOM
165   };
166
167   struct CursorImpl
168   {
169     CursorImpl()
170     : color( Dali::Color::BLACK ),
171       position(),
172       cursorHeight( 0.0f ),
173       lineHeight( 0.0f )
174     {
175     }
176
177     Vector4 color;
178     Vector2 position;
179     float cursorHeight;
180     float lineHeight;
181   };
182
183   struct HandleImpl
184   {
185     HandleImpl()
186     : position(),
187       lineHeight( 0.0f ),
188       grabDisplacementX( 0.f ),
189       grabDisplacementY( 0.f ),
190       active( false ),
191       visible( false ),
192       flipped( false )
193     {
194     }
195
196     ImageActor actor;
197     Actor grabArea;
198
199     Vector2 position;
200     float lineHeight; ///< Not the handle height
201     float grabDisplacementX;
202     float grabDisplacementY;
203     bool active  : 1;
204     bool visible : 1;
205     bool flipped : 1;
206   };
207
208   Impl( Dali::Toolkit::Internal::Control& parent, Observer& observer )
209   : mTextControlParent( parent ),
210     mObserver( observer ),
211     mBoundingBox( Rect<int>() ),
212     mHighlightColor( LIGHT_BLUE ),
213     mActiveCursor( ACTIVE_CURSOR_NONE ),
214     mCursorBlinkInterval( CURSOR_BLINK_INTERVAL ),
215     mCursorBlinkDuration( 0.0f ),
216     mHandleScrolling( HANDLE_TYPE_COUNT ),
217     mScrollDirection( SCROLL_NONE ),
218     mScrollThreshold( SCROLL_THRESHOLD ),
219     mScrollSpeed( SCROLL_SPEED ),
220     mScrollDistance( SCROLL_DISTANCE ),
221     mActiveCopyPastePopup( false ),
222     mCursorBlinkStatus( true ),
223     mPrimaryCursorVisible( false ),
224     mSecondaryCursorVisible( false )
225   {
226   }
227
228   /**
229    * Relayout of the decorations owned by the decorator.
230    * @param[in] size The Size of the UI control the decorator is adding it's decorations to.
231    */
232   void Relayout( const Vector2& size )
233   {
234     // TODO - Remove this if nothing is active
235     CreateActiveLayer();
236
237     /*
238     // Show or hide the cursors
239     CreateCursors();
240     if( mPrimaryCursor )
241     {
242       const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
243       mPrimaryCursorVisible = ( cursor.position.x <= size.width ) && ( cursor.position.x >= 0.f );
244       if( mPrimaryCursorVisible )
245       {
246         Vector2 position = cursor.position;
247         if( GRAB_HANDLE == mHandleScrolling )
248         {
249           if( mScrollDirection == SCROLL_RIGHT )
250           {
251             position.x = 0.f;
252           }
253           else
254           {
255             position.x = size.width;
256           }
257         }
258
259         mPrimaryCursor.SetPosition( position.x,
260                                     position.y );
261         mPrimaryCursor.SetSize( Size( 1.0f, cursor.cursorHeight ) );
262       }
263       mPrimaryCursor.SetVisible( mPrimaryCursorVisible );
264     }
265     if( mSecondaryCursor )
266     {
267       const CursorImpl& cursor = mCursor[SECONDARY_CURSOR];
268       mSecondaryCursorVisible = ( cursor.position.x <= size.width ) && ( cursor.position.x >= 0.f );
269       if( mSecondaryCursorVisible )
270       {
271         mSecondaryCursor.SetPosition( cursor.position.x,
272                                       cursor.position.y );
273         mSecondaryCursor.SetSize( Size( 1.0f, cursor.cursorHeight ) );
274       }
275       mSecondaryCursor.SetVisible( mSecondaryCursorVisible );
276     }
277     */
278     // Show or hide the grab handle
279     HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
280     if( grabHandle.active )
281     {
282       Vector2 position = grabHandle.position;
283
284       if( GRAB_HANDLE == mHandleScrolling )
285       {
286         if( mScrollDirection == SCROLL_RIGHT )
287         {
288           position.x = 0.f;
289         }
290         else
291         {
292           position.x = size.width;
293         }
294       }
295
296       const bool isVisible = ( position.x <= size.width ) && ( position.x >= 0.f );
297
298       if( isVisible )
299       {
300         SetupTouchEvents();
301
302         CreateGrabHandle();
303
304         grabHandle.actor.SetPosition( position.x,
305                                       position.y + grabHandle.lineHeight );
306       }
307       grabHandle.actor.SetVisible( isVisible );
308     }
309     else if( grabHandle.actor )
310     {
311       UnparentAndReset( grabHandle.actor );
312     }
313
314     // Show or hide the selection handles/highlight
315     HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
316     HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
317     if( primary.active || secondary.active )
318     {
319       SetupTouchEvents();
320
321       CreateSelectionHandles();
322
323       primary.actor.SetPosition( primary.position.x,
324                                  primary.position.y + primary.lineHeight );
325
326       secondary.actor.SetPosition( secondary.position.x,
327                                    secondary.position.y + secondary.lineHeight );
328
329       CreateHighlight();
330       UpdateHighlight();
331     }
332     else
333     {
334       UnparentAndReset( primary.actor );
335       UnparentAndReset( secondary.actor );
336       //UnparentAndReset( mHighlightMeshActor );
337     }
338
339     if ( mActiveCopyPastePopup )
340     {
341       if ( !mCopyPastePopup )
342       {
343         mCopyPastePopup = TextSelectionPopup::New();
344 #ifdef DECORATOR_DEBUG
345         mCopyPastePopup.SetName("mCopyPastePopup");
346 #endif
347         mCopyPastePopup.SetAnchorPoint( AnchorPoint::CENTER );
348         mCopyPastePopup.OnRelayoutSignal().Connect( this,  &Decorator::Impl::PopUpRelayoutComplete  ); // Position popup after size negotiation
349         mActiveLayer.Add ( mCopyPastePopup );
350       }
351     }
352     else
353     {
354      if ( mCopyPastePopup )
355      {
356        UnparentAndReset( mCopyPastePopup );
357      }
358     }
359   }
360
361   void UpdatePositions( const Vector2& scrollOffset )
362   {
363     mCursor[PRIMARY_CURSOR].position += scrollOffset;
364     mCursor[SECONDARY_CURSOR].position += scrollOffset;
365     mHandle[ GRAB_HANDLE ].position += scrollOffset;
366     mHandle[ LEFT_SELECTION_HANDLE ].position += scrollOffset;
367     mHandle[ RIGHT_SELECTION_HANDLE ].position += scrollOffset;
368
369     // TODO Highlight box??
370   }
371
372   void PopUpRelayoutComplete( Actor actor )
373   {
374     // Size negotiation for CopyPastePopup complete so can get the size and constrain position within bounding box.
375
376     mCopyPastePopup.OnRelayoutSignal().Disconnect( this, &Decorator::Impl::PopUpRelayoutComplete  );
377
378     Vector3 popupPosition( mCursor[PRIMARY_CURSOR].position.x, mCursor[PRIMARY_CURSOR].position.y -100.0f , 0.0f); //todo 100 to be an offset Property
379
380     Vector3 popupSize = Vector3( mCopyPastePopup.GetRelayoutSize( Dimension::WIDTH ), mCopyPastePopup.GetRelayoutSize( Dimension::HEIGHT ), 0.0f );
381
382     GetConstrainedPopupPosition( popupPosition, popupSize, AnchorPoint::CENTER, mActiveLayer, mBoundingBox );
383
384     SetUpPopUpPositionNotifications();
385
386     mCopyPastePopup.SetPosition( popupPosition ); //todo grabhandle(cursor) or selection handle positions to be used
387   }
388
389   void CreateCursor( ImageActor& cursor, const Vector4& color )
390   {
391     cursor = CreateSolidColorActor( color );
392     cursor.SetParentOrigin( ParentOrigin::TOP_LEFT ); // Need to set the default parent origin as CreateSolidColorActor() sets a different one.
393     cursor.SetAnchorPoint( AnchorPoint::TOP_CENTER );
394   }
395
396   // Add or Remove cursor(s) from parent
397   void CreateCursors()
398   {
399     /*
400     if( mActiveCursor == ACTIVE_CURSOR_NONE )
401     {
402       UnparentAndReset( mPrimaryCursor );
403       UnparentAndReset( mSecondaryCursor );
404     }
405     else
406     {
407       // Create Primary and or Secondary Cursor(s) if active and add to parent
408       if ( mActiveCursor == ACTIVE_CURSOR_PRIMARY ||
409            mActiveCursor == ACTIVE_CURSOR_BOTH )
410       {
411         if ( !mPrimaryCursor )
412         {
413           CreateCursor( mPrimaryCursor, mCursor[PRIMARY_CURSOR].color );
414 #ifdef DECORATOR_DEBUG
415           mPrimaryCursor.SetName( "PrimaryCursorActor" );
416 #endif
417           mActiveLayer.Add( mPrimaryCursor );
418         }
419       }
420
421       if ( mActiveCursor == ACTIVE_CURSOR_BOTH )
422       {
423         if ( !mSecondaryCursor )
424         {
425           CreateCursor( mSecondaryCursor, mCursor[SECONDARY_CURSOR].color );
426 #ifdef DECORATOR_DEBUG
427           mSecondaryCursor.SetName( "SecondaryCursorActor" );
428 #endif
429           mActiveLayer.Add( mSecondaryCursor );
430         }
431       }
432       else
433       {
434         UnparentAndReset( mSecondaryCursor );
435       }
436     }
437     */
438   }
439
440   bool OnCursorBlinkTimerTick()
441   {
442     /*
443     // Cursor blinking
444     if ( mPrimaryCursor )
445     {
446       mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
447     }
448     if ( mSecondaryCursor )
449     {
450       mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
451     }
452
453     mCursorBlinkStatus = !mCursorBlinkStatus;
454     */
455     return true;
456   }
457
458   void SetupTouchEvents()
459   {
460     if ( !mTapDetector )
461     {
462       mTapDetector = TapGestureDetector::New();
463       mTapDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnTap );
464     }
465
466     if ( !mPanGestureDetector )
467     {
468       mPanGestureDetector = PanGestureDetector::New();
469       mPanGestureDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan );
470     }
471   }
472
473   void CreateActiveLayer()
474   {
475     if( !mActiveLayer )
476     {
477       Actor parent = mTextControlParent.Self();
478
479       mActiveLayer = Layer::New();
480 #ifdef DECORATOR_DEBUG
481       mActiveLayer.SetName ( "ActiveLayerActor" );
482 #endif
483
484       mActiveLayer.SetParentOrigin( ParentOrigin::CENTER );
485       mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
486       mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
487
488       parent.Add( mActiveLayer );
489     }
490
491     mActiveLayer.RaiseToTop();
492   }
493
494   void CreateGrabHandle()
495   {
496     HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
497     if( !grabHandle.actor )
498     {
499       if( !mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] )
500       {
501         mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] = ResourceImage::New( DEFAULT_GRAB_HANDLE_IMAGE_RELEASED );
502       }
503       if( !mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_PRESSED] )
504       {
505         mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_PRESSED] = ResourceImage::New( DEFAULT_GRAB_HANDLE_IMAGE_PRESSED );
506       }
507
508       grabHandle.actor = ImageActor::New( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] );
509       grabHandle.actor.SetAnchorPoint( AnchorPoint::TOP_CENTER );
510       grabHandle.actor.SetDrawMode( DrawMode::OVERLAY );
511       // Area that Grab handle responds to, larger than actual handle so easier to move
512 #ifdef DECORATOR_DEBUG
513       grabHandle.actor.SetName( "GrabHandleActor" );
514       if ( Dali::Internal::gLogFilter->IsEnabledFor( Debug::Verbose ) )
515       {
516         grabHandle.grabArea = Toolkit::CreateSolidColorActor( Vector4(0.0f, 0.0f, 0.0f, 0.0f), true, Color::RED, 1 );
517         grabHandle.grabArea.SetName( "GrabArea" );
518       }
519       else
520       {
521         grabHandle.grabArea = Actor::New();
522         grabHandle.grabArea.SetName( "GrabArea" );
523       }
524 #else
525       grabHandle.grabArea = Actor::New();
526 #endif
527
528       grabHandle.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
529       grabHandle.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
530       grabHandle.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
531       grabHandle.grabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
532       grabHandle.actor.Add( grabHandle.grabArea );
533
534       mTapDetector.Attach( grabHandle.grabArea );
535       mPanGestureDetector.Attach( grabHandle.grabArea );
536
537       mActiveLayer.Add( grabHandle.actor );
538     }
539   }
540
541   void CreateSelectionHandles()
542   {
543     HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
544     if( !primary.actor )
545     {
546       if( !mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
547       {
548         mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] = ResourceImage::New( DEFAULT_SELECTION_HANDLE_ONE );
549       }
550
551       primary.actor = ImageActor::New( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
552 #ifdef DECORATOR_DEBUG
553       primary.actor.SetName("SelectionHandleOne");
554 #endif
555       primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
556       primary.actor.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
557       primary.flipped = false;
558
559       primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
560 #ifdef DECORATOR_DEBUG
561       primary.grabArea.SetName("SelectionHandleOneGrabArea");
562 #endif
563       primary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
564       primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
565       primary.grabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
566
567       mTapDetector.Attach( primary.grabArea );
568       mPanGestureDetector.Attach( primary.grabArea );
569       primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched );
570
571       primary.actor.Add( primary.grabArea );
572       mActiveLayer.Add( primary.actor );
573     }
574
575     HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
576     if( !secondary.actor )
577     {
578       if( !mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
579       {
580         mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] = ResourceImage::New( DEFAULT_SELECTION_HANDLE_TWO );
581       }
582
583       secondary.actor = ImageActor::New( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
584 #ifdef DECORATOR_DEBUG
585       secondary.actor.SetName("SelectionHandleTwo");
586 #endif
587       secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text.
588       secondary.actor.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
589       secondary.flipped = false;
590
591       secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
592 #ifdef DECORATOR_DEBUG
593       secondary.grabArea.SetName("SelectionHandleTwoGrabArea");
594 #endif
595       secondary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
596       secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
597       secondary.grabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
598
599       mTapDetector.Attach( secondary.grabArea );
600       mPanGestureDetector.Attach( secondary.grabArea );
601       secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched );
602
603       secondary.actor.Add( secondary.grabArea );
604       mActiveLayer.Add( secondary.actor );
605     }
606   }
607
608   void CreateHighlight()
609   {
610     /*
611     if ( !mHighlightMeshActor )
612     {
613       mHighlightMaterial = Material::New( "HighlightMaterial" );
614       mHighlightMaterial.SetDiffuseColor( mHighlightColor );
615
616       mHighlightMeshData.SetMaterial( mHighlightMaterial );
617       mHighlightMeshData.SetHasNormals( true );
618
619       mHighlightMesh = Mesh::New( mHighlightMeshData );
620
621       mHighlightMeshActor = MeshActor::New( mHighlightMesh );
622 #ifdef DECORATOR_DEBUG
623       mHighlightMeshActor.SetName( "HighlightMeshActor" );
624 #endif
625       mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
626       mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
627
628       Actor parent = mTextControlParent.Self();
629       parent.Add( mHighlightMeshActor );
630     }
631     */
632   }
633
634   void UpdateHighlight()
635   {
636     //  Construct a Mesh with a texture to be used as the highlight 'box' for selected text
637     //
638     //  Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
639     //
640     //   [ TOP   ]  [ TOP ]      [TOP ]  [ TOP    ]      [ TOP  ]      [ TOP  ]
641     //  [ MIDDLE ]             [BOTTOM]  [BOTTOM]      [ MIDDLE ]   [ MIDDLE  ]
642     //  [ BOTTOM]                                      [ MIDDLE ]   [ MIDDLE  ]
643     //                                                 [BOTTOM]     [ MIDDLE  ]
644     //                                                              [BOTTOM]
645     //
646     //  Each quad is created as 2 triangles.
647     //  Middle is just 1 quad regardless of its size.
648     //
649     //  (0,0)         (0,0)
650     //     0*    *2     0*       *2
651     //     TOP          TOP
652     //     3*    *1     3*       *1
653     //  4*       *1     4*     *6
654     //     MIDDLE         BOTTOM
655     //  6*       *5     7*     *5
656     //  6*    *8
657     //   BOTTOM
658     //  9*    *7
659     //
660
661     /*
662     if ( mHighlightMesh && mHighlightMaterial && !mHighlightQuadList.empty() )
663     {
664       MeshData::VertexContainer vertices;
665       Dali::MeshData::FaceIndices faceIndices;
666
667       std::vector<QuadCoordinates>::iterator iter = mHighlightQuadList.begin();
668       std::vector<QuadCoordinates>::iterator endIter = mHighlightQuadList.end();
669
670       // vertex position defaults to (0 0 0)
671       MeshData::Vertex vertex;
672       // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
673       vertex.nZ = 1.0f;
674
675       for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
676       {
677         // Add each quad geometry (a sub-selection) to the mesh data.
678
679         // 0-----1
680         // |\    |
681         // | \ A |
682         // |  \  |
683         // | B \ |
684         // |    \|
685         // 2-----3
686
687         QuadCoordinates& quad = *iter;
688         // top-left (v+0)
689         vertex.x = quad.min.x;
690         vertex.y = quad.min.y;
691         vertices.push_back( vertex );
692
693         // top-right (v+1)
694         vertex.x = quad.max.x;
695         vertex.y = quad.min.y;
696         vertices.push_back( vertex );
697
698         // bottom-left (v+2)
699         vertex.x = quad.min.x;
700         vertex.y = quad.max.y;
701         vertices.push_back( vertex );
702
703         // bottom-right (v+3)
704         vertex.x = quad.max.x;
705         vertex.y = quad.max.y;
706         vertices.push_back( vertex );
707
708         // triangle A (3, 1, 0)
709         faceIndices.push_back( v + 3 );
710         faceIndices.push_back( v + 1 );
711         faceIndices.push_back( v );
712
713         // triangle B (0, 2, 3)
714         faceIndices.push_back( v );
715         faceIndices.push_back( v + 2 );
716         faceIndices.push_back( v + 3 );
717
718         mHighlightMeshData.SetFaceIndices( faceIndices );
719       }
720
721       BoneContainer bones(0); // passed empty as bones not required
722       mHighlightMeshData.SetData( vertices, faceIndices, bones, mHighlightMaterial );
723       mHighlightMesh.UpdateMeshData( mHighlightMeshData );
724     }
725     */
726   }
727
728   void OnTap( Actor actor, const TapGesture& tap )
729   {
730     if( actor == mHandle[GRAB_HANDLE].actor )
731     {
732       // TODO
733     }
734   }
735
736   void DoPan( HandleImpl& handle, HandleType type, const PanGesture& gesture )
737   {
738     if( Gesture::Started == gesture.state )
739     {
740       handle.grabDisplacementX = handle.grabDisplacementY = 0;
741       if( mHandleImages[type][HANDLE_IMAGE_PRESSED] )
742       {
743         handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_PRESSED] );
744       }
745     }
746
747     handle.grabDisplacementX += gesture.displacement.x;
748     handle.grabDisplacementY += gesture.displacement.y;
749
750     const float x = handle.position.x + handle.grabDisplacementX;
751     const float y = handle.position.y + handle.lineHeight*0.5f + handle.grabDisplacementY;
752
753     if( Gesture::Started    == gesture.state ||
754         Gesture::Continuing == gesture.state )
755     {
756       if( x < mScrollThreshold )
757       {
758         mScrollDirection = SCROLL_RIGHT;
759         mHandleScrolling = type;
760         StartScrollTimer();
761       }
762       else if( x > mTextControlParent.GetControlSize().width - mScrollThreshold )
763       {
764         mScrollDirection = SCROLL_LEFT;
765         mHandleScrolling = type;
766         StartScrollTimer();
767       }
768       else
769       {
770         mHandleScrolling = HANDLE_TYPE_COUNT;
771         StopScrollTimer();
772         mObserver.HandleEvent( type, HANDLE_PRESSED, x, y );
773       }
774     }
775     else if( Gesture::Finished  == gesture.state ||
776              Gesture::Cancelled == gesture.state )
777     {
778       if( mScrollTimer && mScrollTimer.IsRunning() )
779       {
780         mHandleScrolling = HANDLE_TYPE_COUNT;
781         StopScrollTimer();
782         mObserver.HandleEvent( type, HANDLE_STOP_SCROLLING, x, y );
783       }
784       else
785       {
786         mObserver.HandleEvent( type, HANDLE_RELEASED, x, y );
787       }
788       handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_RELEASED] );
789     }
790   }
791
792   void OnPan( Actor actor, const PanGesture& gesture )
793   {
794     HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
795     HandleImpl& primarySelectionHandle = mHandle[LEFT_SELECTION_HANDLE];
796     HandleImpl& secondarySelectionHandle = mHandle[RIGHT_SELECTION_HANDLE];
797
798     if( actor == grabHandle.grabArea )
799     {
800       DoPan( grabHandle, GRAB_HANDLE, gesture );
801     }
802     else if( actor == primarySelectionHandle.grabArea )
803     {
804       DoPan( primarySelectionHandle, LEFT_SELECTION_HANDLE, gesture );
805     }
806     else if( actor == secondarySelectionHandle.grabArea )
807     {
808       DoPan( secondarySelectionHandle, RIGHT_SELECTION_HANDLE, gesture );
809     }
810   }
811
812   bool OnHandleOneTouched( Actor actor, const TouchEvent& touch )
813   {
814     // TODO
815     return false;
816   }
817
818   bool OnHandleTwoTouched( Actor actor, const TouchEvent& touch )
819   {
820     // TODO
821     return false;
822   }
823
824   // Popup
825
826   float AlternatePopUpPositionRelativeToCursor()
827   {
828     float alternativePosition=0.0f;;
829
830     /*
831     if ( mPrimaryCursor ) // Secondary cursor not used for paste
832     {
833       Cursor cursor = PRIMARY_CURSOR;
834       alternativePosition = mCursor[cursor].position.y;
835     }
836     */
837     const float popupHeight = 120.0f; // todo Set as a MaxSize Property in Control or retrieve from CopyPastePopup class.
838
839     if( mHandle[GRAB_HANDLE].active )
840     {
841       // If grab handle enabled then position pop-up below the grab handle.
842       const Vector2 grabHandleSize( 59.0f, 56.0f ); // todo
843       const float BOTTOM_HANDLE_BOTTOM_OFFSET = 1.5; //todo Should be a property
844       alternativePosition +=  grabHandleSize.height  + popupHeight + BOTTOM_HANDLE_BOTTOM_OFFSET ;
845     }
846     else
847     {
848       alternativePosition += popupHeight;
849     }
850
851     return alternativePosition;
852   }
853
854   void PopUpLeavesVerticalBoundary( PropertyNotification& source )
855   {
856     float alternativeYPosition=0.0f;
857     // todo use AlternatePopUpPositionRelativeToSelectionHandles() if text is highlighted
858     // if can't be positioned above, then position below row.
859     alternativeYPosition = AlternatePopUpPositionRelativeToCursor();
860
861     mCopyPastePopup.SetY( alternativeYPosition );
862   }
863
864
865   void SetUpPopUpPositionNotifications( )
866   {
867     // Note Property notifications ignore any set anchor point so conditions must allow for this.  Default is Top Left.
868
869     // Exceeding vertical boundary
870
871     Vector4 worldCoordinatesBoundingBox;
872     LocalToWorldCoordinatesBoundingBox( mBoundingBox, worldCoordinatesBoundingBox );
873
874     float popupHeight = mCopyPastePopup.GetRelayoutSize( Dimension::HEIGHT);
875
876     PropertyNotification verticalExceedNotification = mCopyPastePopup.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
877                                                       OutsideCondition( worldCoordinatesBoundingBox.y + popupHeight/2,
878                                                                         worldCoordinatesBoundingBox.w - popupHeight/2 ) );
879
880     verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary );
881   }
882
883   void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, Vector3& popupSize, Vector3 anchorPoint, Actor& parent, Rect<int>& boundingBox )
884   {
885     DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() )
886
887     // Parent must already by added to Stage for these Get calls to work
888     Vector3 parentAnchorPoint = parent.GetCurrentAnchorPoint();
889     Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize()*parentAnchorPoint;
890     Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition;  // Parent World position plus popup local position gives World Position
891     Vector3 popupDistanceFromAnchorPoint = popupSize*anchorPoint;
892
893     // Bounding rectangle is supplied as screen coordinates, bounding will be done in world coordinates.
894     Vector4 boundingRectangleWorld;
895     LocalToWorldCoordinatesBoundingBox( boundingBox, boundingRectangleWorld );
896
897     // Calculate distance to move popup (in local space) so fits within the boundary
898     float xOffSetToKeepWithinBounds = 0.0f;
899     if( popupWorldPosition.x - popupDistanceFromAnchorPoint.x < boundingRectangleWorld.x )
900     {
901       xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x );
902     }
903     else if ( popupWorldPosition.x +  popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z )
904     {
905       xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x +  popupDistanceFromAnchorPoint.x );
906     }
907
908     // Ensure initial display of Popup is in alternative position if can not fit above. As Property notification will be a frame behind.
909     if ( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y )
910     {
911       requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor();
912     }
913
914     requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds;
915   }
916
917   void SetScrollThreshold( float threshold )
918   {
919     mScrollThreshold = threshold;
920   }
921
922   float GetScrollThreshold() const
923   {
924     return mScrollThreshold;
925   }
926
927   void SetScrollSpeed( float speed )
928   {
929     mScrollSpeed = speed;
930     mScrollDistance = speed * SCROLL_TICK_INTERVAL * TO_SECONDS;
931   }
932
933   float GetScrollSpeed() const
934   {
935     return mScrollSpeed;
936   }
937
938   /**
939    * Creates and starts a timer to scroll the text when handles are close to the edges of the text.
940    *
941    * It only starts the timer if it's already created.
942    */
943   void StartScrollTimer()
944   {
945     if( !mScrollTimer )
946     {
947       mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
948       mScrollTimer.TickSignal().Connect( this, &Decorator::Impl::OnScrollTimerTick );
949     }
950
951     if( !mScrollTimer.IsRunning() )
952     {
953       mScrollTimer.Start();
954     }
955   }
956
957   /**
958    * Stops the timer used to scroll the text.
959    */
960   void StopScrollTimer()
961   {
962     if( mScrollTimer )
963     {
964       mScrollTimer.Stop();
965     }
966   }
967
968   /**
969    * Callback called by the timer used to scroll the text.
970    *
971    * It calculates and sets a new scroll position.
972    */
973   bool OnScrollTimerTick()
974   {
975     if( HANDLE_TYPE_COUNT != mHandleScrolling )
976     {
977       mObserver.HandleEvent( mHandleScrolling,
978                              HANDLE_SCROLLING,
979                              mScrollDirection == SCROLL_RIGHT ? mScrollDistance : -mScrollDistance,
980                              0.f );
981     }
982
983     return true;
984   }
985
986   Internal::Control&  mTextControlParent;
987   Observer&           mObserver;
988
989   TapGestureDetector  mTapDetector;
990   PanGestureDetector  mPanGestureDetector;
991   Timer               mCursorBlinkTimer;          ///< Timer to signal cursor to blink
992   Timer               mScrollTimer;               ///< Timer used to scroll the text when the grab handle is moved close to the edges.
993
994   Layer               mActiveLayer;               ///< Layer for active handles and alike that ensures they are above all else.
995   //ImageActor          mPrimaryCursor;
996   //ImageActor          mSecondaryCursor;
997   //MeshActor           mHighlightMeshActor;        ///< Mesh Actor to display highlight
998   TextSelectionPopup  mCopyPastePopup;
999
1000   Image               mHandleImages[HANDLE_TYPE_COUNT][HANDLE_IMAGE_TYPE_COUNT];
1001   Image               mCursorImage;
1002   //Mesh                mHighlightMesh;             ///< Mesh for highlight
1003   //MeshData            mHighlightMeshData;         ///< Mesh Data for highlight
1004   //Material            mHighlightMaterial;         ///< Material used for highlight
1005
1006   CursorImpl          mCursor[CURSOR_COUNT];
1007   HandleImpl          mHandle[HANDLE_TYPE_COUNT];
1008   QuadContainer       mHighlightQuadList;         ///< Sub-selections that combine to create the complete selection highlight
1009
1010   Rect<int>           mBoundingBox;
1011   Vector4             mHighlightColor;            ///< Color of the highlight
1012
1013   unsigned int        mActiveCursor;
1014   unsigned int        mCursorBlinkInterval;
1015   float               mCursorBlinkDuration;
1016   HandleType          mHandleScrolling;         ///< The handle which is scrolling.
1017   ScrollDirection     mScrollDirection;         ///< The direction of the scroll.
1018   float               mScrollThreshold;         ///< Defines a square area inside the control, close to the edge. A cursor entering this area will trigger scroll events.
1019   float               mScrollSpeed;             ///< The scroll speed in pixels per second.
1020   float               mScrollDistance;          ///< Distance the text scrolls during a scroll interval.
1021   unsigned int        mScrollInterval;          ///< Time in milliseconds of a scroll interval.
1022
1023   bool                mActiveCopyPastePopup   : 1;
1024   bool                mCursorBlinkStatus      : 1; ///< Flag to switch between blink on and blink off.
1025   bool                mPrimaryCursorVisible   : 1; ///< Whether the primary cursor is visible.
1026   bool                mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible.
1027 };
1028
1029 DecoratorPtr Decorator::New( Internal::Control& parent, Observer& observer )
1030 {
1031   return DecoratorPtr( new Decorator(parent, observer) );
1032 }
1033
1034 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
1035 {
1036   mImpl->mBoundingBox = boundingBox;
1037 }
1038
1039 const Rect<int>& Decorator::GetBoundingBox() const
1040 {
1041   return mImpl->mBoundingBox;
1042 }
1043
1044 void Decorator::Relayout( const Vector2& size )
1045 {
1046   mImpl->Relayout( size );
1047 }
1048
1049 void Decorator::UpdatePositions( const Vector2& scrollOffset )
1050 {
1051   mImpl->UpdatePositions( scrollOffset );
1052 }
1053
1054 /** Cursor **/
1055
1056 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
1057 {
1058   mImpl->mActiveCursor = activeCursor;
1059 }
1060
1061 unsigned int Decorator::GetActiveCursor() const
1062 {
1063   return mImpl->mActiveCursor;
1064 }
1065
1066 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
1067 {
1068   mImpl->mCursor[cursor].position.x = x;
1069   mImpl->mCursor[cursor].position.y = y;
1070   mImpl->mCursor[cursor].cursorHeight = cursorHeight;
1071   mImpl->mCursor[cursor].lineHeight = lineHeight;
1072 }
1073
1074 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
1075 {
1076   x = mImpl->mCursor[cursor].position.x;
1077   y = mImpl->mCursor[cursor].position.y;
1078   cursorHeight = mImpl->mCursor[cursor].cursorHeight;
1079   lineHeight = mImpl->mCursor[cursor].lineHeight;
1080 }
1081
1082 const Vector2& Decorator::GetPosition( Cursor cursor ) const
1083 {
1084   return mImpl->mCursor[cursor].position;
1085 }
1086
1087 void Decorator::SetColor( Cursor cursor, const Dali::Vector4& color )
1088 {
1089   mImpl->mCursor[cursor].color = color;
1090 }
1091
1092 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
1093 {
1094   return mImpl->mCursor[cursor].color;
1095 }
1096
1097 void Decorator::StartCursorBlink()
1098 {
1099   if ( !mImpl->mCursorBlinkTimer )
1100   {
1101     mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
1102     mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
1103   }
1104
1105   if ( !mImpl->mCursorBlinkTimer.IsRunning() )
1106   {
1107     mImpl->mCursorBlinkTimer.Start();
1108   }
1109 }
1110
1111 void Decorator::StopCursorBlink()
1112 {
1113   if ( mImpl->mCursorBlinkTimer )
1114   {
1115     mImpl->mCursorBlinkTimer.Stop();
1116   }
1117 }
1118
1119 void Decorator::SetCursorBlinkInterval( float seconds )
1120 {
1121   mImpl->mCursorBlinkInterval = static_cast<unsigned int>( seconds * TO_MILLISECONDS ); // Convert to milliseconds
1122 }
1123
1124 float Decorator::GetCursorBlinkInterval() const
1125 {
1126   return static_cast<float>( mImpl->mCursorBlinkInterval ) * TO_SECONDS;
1127 }
1128
1129 void Decorator::SetCursorBlinkDuration( float seconds )
1130 {
1131   mImpl->mCursorBlinkDuration = seconds;
1132 }
1133
1134 float Decorator::GetCursorBlinkDuration() const
1135 {
1136   return mImpl->mCursorBlinkDuration;
1137 }
1138
1139 /** Handles **/
1140
1141 void Decorator::SetHandleActive( HandleType handleType, bool active )
1142 {
1143   mImpl->mHandle[handleType].active = active;
1144 }
1145
1146 bool Decorator::IsHandleActive( HandleType handleType ) const
1147 {
1148   return mImpl->mHandle[handleType].active ;
1149 }
1150
1151 void Decorator::SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1152 {
1153   mImpl->mHandleImages[handleType][handleImageType] = image;
1154 }
1155
1156 Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType handleImageType ) const
1157 {
1158   return mImpl->mHandleImages[handleType][handleImageType];
1159 }
1160
1161 void Decorator::SetPosition( HandleType handleType, float x, float y, float height )
1162 {
1163   // Adjust grab handle displacement
1164   Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1165
1166   handle.grabDisplacementX -= x - handle.position.x;
1167   handle.grabDisplacementY -= y - handle.position.y;
1168
1169   handle.position.x = x;
1170   handle.position.y = y;
1171   handle.lineHeight = height;
1172 }
1173
1174 void Decorator::GetPosition( HandleType handleType, float& x, float& y, float& height ) const
1175 {
1176   Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1177
1178   x = handle.position.x;
1179   y = handle.position.y;
1180   height = handle.lineHeight;
1181 }
1182
1183 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1184 {
1185   mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1186 }
1187
1188 void Decorator::ClearHighlights()
1189 {
1190   mImpl->mHighlightQuadList.clear();
1191 }
1192
1193 void Decorator::SetHighlightColor( const Vector4& color )
1194 {
1195   mImpl->mHighlightColor = color;
1196 }
1197
1198 const Vector4& Decorator::GetHighlightColor() const
1199 {
1200   return mImpl->mHighlightColor;
1201 }
1202
1203 void Decorator::SetPopupActive( bool active )
1204 {
1205   mImpl->mActiveCopyPastePopup = active;
1206 }
1207
1208 bool Decorator::IsPopupActive() const
1209 {
1210   return mImpl->mActiveCopyPastePopup ;
1211 }
1212
1213 /** Scroll **/
1214
1215 void Decorator::SetScrollThreshold( float threshold )
1216 {
1217   mImpl->SetScrollThreshold( threshold );
1218 }
1219
1220 float Decorator::GetScrollThreshold() const
1221 {
1222   return mImpl->GetScrollThreshold();
1223 }
1224
1225 void Decorator::SetScrollSpeed( float speed )
1226 {
1227   mImpl->SetScrollSpeed( speed );
1228 }
1229
1230 float Decorator::GetScrollSpeed() const
1231 {
1232   return mImpl->GetScrollSpeed();
1233 }
1234
1235 Decorator::~Decorator()
1236 {
1237   delete mImpl;
1238 }
1239
1240 Decorator::Decorator( Dali::Toolkit::Internal::Control& parent, Observer& observer )
1241 : mImpl( NULL )
1242 {
1243   mImpl = new Decorator::Impl( parent, observer );
1244 }
1245
1246 } // namespace Text
1247
1248 } // namespace Toolkit
1249
1250 } // namespace Dali