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