32e3becd14020deb9c8b53dceee3550f510d3ba2
[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 / 1000.f;
87
88 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( -0.05f );
89
90 /**
91  * structure to hold coordinates of each quad, which will make up the mesh.
92  */
93 struct QuadCoordinates
94 {
95   /**
96    * Default constructor
97    */
98   QuadCoordinates()
99   {
100   }
101
102   /**
103    * Constructor
104    * @param[in] x1 left co-ordinate
105    * @param[in] y1 top co-ordinate
106    * @param[in] x2 right co-ordinate
107    * @param[in] y2 bottom co-ordinate
108    */
109   QuadCoordinates(float x1, float y1, float x2, float y2)
110   : min(x1, y1),
111     max(x2, y2)
112   {
113   }
114
115   Dali::Vector2 min;                          ///< top-left (minimum) position of quad
116   Dali::Vector2 max;                          ///< bottom-right (maximum) position of quad
117 };
118
119 typedef std::vector<QuadCoordinates> QuadContainer;
120
121 /**
122  * @brief Takes a bounding rectangle in the local coordinates of an actor and returns the world coordinates Bounding Box.
123  * @param[in] boundingRectangle local bounding
124  * @param[out] Vector4 World coordinate bounding Box.
125  */
126 void LocalToWorldCoordinatesBoundingBox( const Dali::Rect<int>& boundingRectangle, Dali::Vector4& boundingBox )
127 {
128   // Convert to world coordinates and store as a Vector4 to be compatible with Property Notifications.
129   Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
130
131   const float originX = boundingRectangle.x - 0.5f * stageSize.width;
132   const float originY = boundingRectangle.y - 0.5f * stageSize.height;
133
134   boundingBox = Dali::Vector4( originX,
135                                originY,
136                                originX + boundingRectangle.width,
137                                originY + boundingRectangle.height );
138 }
139
140
141 } // end of namespace
142
143 namespace Dali
144 {
145
146 namespace Toolkit
147 {
148
149 namespace Text
150 {
151
152 struct Decorator::Impl : public ConnectionTracker
153 {
154   struct CursorImpl
155   {
156     CursorImpl()
157     : color( Dali::Color::WHITE ),
158       position(),
159       cursorHeight( 0.0f ),
160       lineHeight( 0.0f )
161     {
162     }
163
164     Vector4 color;
165     Vector2 position;
166     float cursorHeight;
167     float lineHeight;
168   };
169
170   struct SelectionHandleImpl
171   {
172     SelectionHandleImpl()
173     : position(),
174       lineHeight( 0.0f ),
175       flipped( false )
176     {
177     }
178
179     ImageActor actor;
180     Actor grabArea;
181
182     Image pressedImage;
183     Image releasedImage;
184
185     Vector2 position;
186     float lineHeight; ///< Not the handle height
187     bool flipped;
188   };
189
190   Impl( Dali::Toolkit::Internal::Control& parent, Observer& observer )
191   : mTextControlParent( parent ),
192     mObserver( observer ),
193     mBoundingBox( Rect<int>() ),
194     mHighlightColor( 0.07f, 0.41f, 0.59f, 1.0f ), // light blue
195     mActiveCursor( ACTIVE_CURSOR_NONE ),
196     mCursorBlinkInterval( CURSOR_BLINK_INTERVAL ),
197     mCursorBlinkDuration( 0.0f ),
198     mGrabDisplacementX( 0.0f ),
199     mGrabDisplacementY( 0.0f ),
200     mActiveGrabHandle( false ),
201     mActiveSelection( false ),
202     mActiveCopyPastePopup( false ),
203     mCursorBlinkStatus( true ),
204     mPrimaryCursorVisible( false ),
205     mSecondaryCursorVisible( false )
206   {
207   }
208
209   /**
210    * Relayout of the decorations owned by the decorator.
211    * @param[in] size The Size of the UI control the decorator is adding it's decorations to.
212    */
213   void Relayout( const Vector2& size )
214   {
215     // TODO - Remove this if nothing is active
216     CreateActiveLayer();
217
218     // Show or hide the cursors
219     CreateCursors();
220     if( mPrimaryCursor )
221     {
222       mPrimaryCursorVisible = ( mCursor[PRIMARY_CURSOR].position.x <= size.width ) && ( mCursor[PRIMARY_CURSOR].position.x >= 0.f );
223       if( mPrimaryCursorVisible )
224       {
225         mPrimaryCursor.SetPosition( mCursor[PRIMARY_CURSOR].position.x,
226                                     mCursor[PRIMARY_CURSOR].position.y );
227         mPrimaryCursor.SetSize( Size( 1.0f, mCursor[PRIMARY_CURSOR].cursorHeight ) );
228       }
229       mPrimaryCursor.SetVisible( mPrimaryCursorVisible );
230     }
231     if( mSecondaryCursor )
232     {
233       mSecondaryCursorVisible = ( mCursor[SECONDARY_CURSOR].position.x <= size.width ) && ( mCursor[SECONDARY_CURSOR].position.x >= 0.f );
234       if( mSecondaryCursorVisible )
235       {
236         mSecondaryCursor.SetPosition( mCursor[SECONDARY_CURSOR].position.x,
237                                       mCursor[SECONDARY_CURSOR].position.y );
238         mSecondaryCursor.SetSize( Size( 1.0f, mCursor[SECONDARY_CURSOR].cursorHeight ) );
239       }
240       mSecondaryCursor.SetVisible( mSecondaryCursorVisible );
241     }
242
243     // Show or hide the grab handle
244     if( mActiveGrabHandle )
245     {
246       const bool isVisible = ( mCursor[PRIMARY_CURSOR].position.x <= size.width ) && ( mCursor[PRIMARY_CURSOR].position.x >= 0.f );
247
248       if( isVisible )
249       {
250         SetupTouchEvents();
251
252         CreateGrabHandle();
253
254         mGrabHandle.SetPosition( mCursor[PRIMARY_CURSOR].position.x,
255                                  mCursor[PRIMARY_CURSOR].position.y + mCursor[PRIMARY_CURSOR].lineHeight );
256       }
257       mGrabHandle.SetVisible( isVisible );
258     }
259     else if( mGrabHandle )
260     {
261       UnparentAndReset( mGrabHandle );
262     }
263
264     // Show or hide the selection handles/highlight
265     if( mActiveSelection )
266     {
267       SetupTouchEvents();
268
269       CreateSelectionHandles();
270
271       SelectionHandleImpl& primary = mSelectionHandle[ PRIMARY_SELECTION_HANDLE ];
272       primary.actor.SetPosition( primary.position.x,
273                                  primary.position.y + primary.lineHeight );
274
275       SelectionHandleImpl& secondary = mSelectionHandle[ SECONDARY_SELECTION_HANDLE ];
276       secondary.actor.SetPosition( secondary.position.x,
277                                    secondary.position.y + secondary.lineHeight );
278
279       CreateHighlight();
280       UpdateHighlight();
281     }
282     else
283     {
284       UnparentAndReset( mSelectionHandle[ PRIMARY_SELECTION_HANDLE ].actor );
285       UnparentAndReset( mSelectionHandle[ SECONDARY_SELECTION_HANDLE ].actor );
286       UnparentAndReset( mHighlightMeshActor );
287     }
288
289     if ( mActiveCopyPastePopup )
290     {
291       if ( !mCopyPastePopup )
292       {
293         mCopyPastePopup = TextSelectionPopup::New();
294 #ifdef DECORATOR_DEBUG
295         mCopyPastePopup.SetName("mCopyPastePopup");
296 #endif
297         mCopyPastePopup.SetAnchorPoint( AnchorPoint::CENTER );
298         mCopyPastePopup.OnRelayoutSignal().Connect( this,  &Decorator::Impl::PopUpRelayoutComplete  ); // Position popup after size negotiation
299         mActiveLayer.Add ( mCopyPastePopup );
300       }
301     }
302     else
303     {
304      if ( mCopyPastePopup )
305      {
306        UnparentAndReset( mCopyPastePopup );
307      }
308     }
309   }
310
311   void UpdatePositions( const Vector2& scrollOffset )
312   {
313     mCursor[PRIMARY_CURSOR].position += scrollOffset;
314     mCursor[SECONDARY_CURSOR].position += scrollOffset;
315     mSelectionHandle[ PRIMARY_SELECTION_HANDLE ].position += scrollOffset;
316     mSelectionHandle[ SECONDARY_SELECTION_HANDLE ].position += scrollOffset;
317
318     // TODO Highlight box??
319   }
320
321   void PopUpRelayoutComplete( Actor actor )
322   {
323     // Size negotiation for CopyPastePopup complete so can get the size and constrain position within bounding box.
324
325     mCopyPastePopup.OnRelayoutSignal().Disconnect( this, &Decorator::Impl::PopUpRelayoutComplete  );
326
327     Vector3 popupPosition( mCursor[PRIMARY_CURSOR].position.x, mCursor[PRIMARY_CURSOR].position.y -100.0f , 0.0f); //todo 100 to be an offset Property
328
329     Vector3 popupSize = Vector3( mCopyPastePopup.GetRelayoutSize( Dimension::WIDTH ), mCopyPastePopup.GetRelayoutSize( Dimension::HEIGHT ), 0.0f );
330
331     GetConstrainedPopupPosition( popupPosition, popupSize, AnchorPoint::CENTER, mActiveLayer, mBoundingBox );
332
333     SetUpPopUpPositionNotifications();
334
335     mCopyPastePopup.SetPosition( popupPosition ); //todo grabhandle(cursor) or selection handle positions to be used
336   }
337
338   void CreateCursor( ImageActor& cursor )
339   {
340     cursor = CreateSolidColorActor( Color::WHITE );
341     cursor.SetParentOrigin( ParentOrigin::TOP_LEFT ); // Need to set the default parent origin as CreateSolidColorActor() sets a different one.
342     cursor.SetAnchorPoint( AnchorPoint::TOP_CENTER );
343   }
344
345   // Add or Remove cursor(s) from parent
346   void CreateCursors()
347   {
348     if( mActiveCursor == ACTIVE_CURSOR_NONE )
349     {
350       UnparentAndReset( mPrimaryCursor );
351       UnparentAndReset( mSecondaryCursor );
352     }
353     else
354     {
355       /* Create Primary and or Secondary Cursor(s) if active and add to parent */
356       if ( mActiveCursor == ACTIVE_CURSOR_PRIMARY ||
357            mActiveCursor == ACTIVE_CURSOR_BOTH )
358       {
359         if ( !mPrimaryCursor )
360         {
361           CreateCursor( mPrimaryCursor );
362 #ifdef DECORATOR_DEBUG
363           mPrimaryCursor.SetName( "PrimaryCursorActor" );
364 #endif
365           mActiveLayer.Add( mPrimaryCursor );
366         }
367       }
368
369       if ( mActiveCursor == ACTIVE_CURSOR_BOTH )
370       {
371         if ( !mSecondaryCursor )
372         {
373           CreateCursor( mSecondaryCursor );
374 #ifdef DECORATOR_DEBUG
375           mSecondaryCursor.SetName( "SecondaryCursorActor" );
376 #endif
377           mActiveLayer.Add( mSecondaryCursor );
378         }
379       }
380       else
381       {
382         UnparentAndReset( mSecondaryCursor );
383       }
384     }
385   }
386
387   bool OnCursorBlinkTimerTick()
388   {
389     // Cursor blinking
390     if ( mPrimaryCursor )
391     {
392       mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
393     }
394     if ( mSecondaryCursor )
395     {
396       mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
397     }
398
399     mCursorBlinkStatus = !mCursorBlinkStatus;
400
401     return true;
402   }
403
404   void SetupTouchEvents()
405   {
406     if ( !mTapDetector )
407     {
408       mTapDetector = TapGestureDetector::New();
409       mTapDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnTap );
410     }
411
412     if ( !mPanGestureDetector )
413     {
414       mPanGestureDetector = PanGestureDetector::New();
415       mPanGestureDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan );
416     }
417   }
418
419   void CreateActiveLayer()
420   {
421     if( !mActiveLayer )
422     {
423       Actor parent = mTextControlParent.Self();
424
425       mActiveLayer = Layer::New();
426 #ifdef DECORATOR_DEBUG
427       mActiveLayer.SetName ( "ActiveLayerActor" );
428 #endif
429
430       mActiveLayer.SetParentOrigin( ParentOrigin::CENTER );
431       mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
432       mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
433
434       parent.Add( mActiveLayer );
435     }
436
437     mActiveLayer.RaiseToTop();
438   }
439
440   void CreateGrabHandle()
441   {
442     if( !mGrabHandle )
443     {
444       if ( !mGrabHandleImage )
445       {
446         mGrabHandleImage = ResourceImage::New( DEFAULT_GRAB_HANDLE_IMAGE );
447       }
448
449       mGrabHandle = ImageActor::New( mGrabHandleImage );
450       mGrabHandle.SetAnchorPoint( AnchorPoint::TOP_CENTER );
451       mGrabHandle.SetDrawMode( DrawMode::OVERLAY );
452       // Area that Grab handle responds to, larger than actual handle so easier to move
453 #ifdef DECORATOR_DEBUG
454       mGrabHandle.SetName( "GrabHandleActor" );
455       if ( Dali::Internal::gLogFilter->IsEnabledFor( Debug::Verbose ) )
456       {
457         mGrabArea = Toolkit::CreateSolidColorActor( Vector4(0.0f, 0.0f, 0.0f, 0.0f), true, Color::RED, 1 );
458         mGrabArea.SetName( "GrabArea" );
459       }
460       else
461       {
462         mGrabArea = Actor::New();
463         mGrabArea.SetName( "GrabArea" );
464       }
465 #else
466       mGrabArea = Actor::New();
467 #endif
468
469       mGrabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
470       mGrabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
471       mGrabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
472       mGrabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
473       mGrabHandle.Add( mGrabArea );
474
475       mTapDetector.Attach( mGrabArea );
476       mPanGestureDetector.Attach( mGrabArea );
477
478       mActiveLayer.Add( mGrabHandle );
479     }
480   }
481
482   void CreateSelectionHandles()
483   {
484     SelectionHandleImpl& primary = mSelectionHandle[ PRIMARY_SELECTION_HANDLE ];
485     if ( !primary.actor )
486     {
487       if ( !primary.releasedImage )
488       {
489         primary.releasedImage = ResourceImage::New( DEFAULT_SELECTION_HANDLE_ONE );
490       }
491
492       primary.actor = ImageActor::New( primary.releasedImage );
493 #ifdef DECORATOR_DEBUG
494       primary.actor.SetName("SelectionHandleOne");
495 #endif
496       primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
497       primary.actor.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
498       primary.flipped = false;
499
500       primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
501 #ifdef DECORATOR_DEBUG
502       primary.grabArea.SetName("SelectionHandleOneGrabArea");
503 #endif
504       primary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
505       primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
506       primary.grabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
507
508       mTapDetector.Attach( primary.grabArea );
509       mPanGestureDetector.Attach( primary.grabArea );
510       primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched );
511
512       primary.actor.Add( primary.grabArea );
513       mActiveLayer.Add( primary.actor );
514     }
515
516     SelectionHandleImpl& secondary = mSelectionHandle[ SECONDARY_SELECTION_HANDLE ];
517     if ( !secondary.actor )
518     {
519       if ( !secondary.releasedImage )
520       {
521         secondary.releasedImage = ResourceImage::New( DEFAULT_SELECTION_HANDLE_TWO );
522       }
523
524       secondary.actor = ImageActor::New( secondary.releasedImage );
525 #ifdef DECORATOR_DEBUG
526       secondary.actor.SetName("SelectionHandleTwo");
527 #endif
528       secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text.
529       secondary.actor.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
530       secondary.flipped = false;
531
532       secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
533 #ifdef DECORATOR_DEBUG
534       secondary.grabArea.SetName("SelectionHandleTwoGrabArea");
535 #endif
536       secondary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
537       secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
538       secondary.grabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
539
540       mTapDetector.Attach( secondary.grabArea );
541       mPanGestureDetector.Attach( secondary.grabArea );
542       secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched );
543
544       secondary.actor.Add( secondary.grabArea );
545       mActiveLayer.Add( secondary.actor );
546     }
547
548     //SetUpHandlePropertyNotifications(); TODO
549   }
550
551   void CreateHighlight()
552   {
553     if ( !mHighlightMeshActor )
554     {
555       mHighlightMaterial = Material::New( "HighlightMaterial" );
556       mHighlightMaterial.SetDiffuseColor( mHighlightColor );
557
558       mHighlightMeshData.SetMaterial( mHighlightMaterial );
559       mHighlightMeshData.SetHasNormals( true );
560
561       mHighlightMesh = Mesh::New( mHighlightMeshData );
562
563       mHighlightMeshActor = MeshActor::New( mHighlightMesh );
564 #ifdef DECORATOR_DEBUG
565       mHighlightMeshActor.SetName( "HighlightMeshActor" );
566 #endif
567       mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
568       mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
569
570       Actor parent = mTextControlParent.Self();
571       parent.Add( mHighlightMeshActor );
572     }
573   }
574
575   void UpdateHighlight()
576   {
577     //  Construct a Mesh with a texture to be used as the highlight 'box' for selected text
578     //
579     //  Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
580     //
581     //   [ TOP   ]  [ TOP ]      [TOP ]  [ TOP    ]      [ TOP  ]      [ TOP  ]
582     //  [ MIDDLE ]             [BOTTOM]  [BOTTOM]      [ MIDDLE ]   [ MIDDLE  ]
583     //  [ BOTTOM]                                      [ MIDDLE ]   [ MIDDLE  ]
584     //                                                 [BOTTOM]     [ MIDDLE  ]
585     //                                                              [BOTTOM]
586     //
587     //  Each quad is created as 2 triangles.
588     //  Middle is just 1 quad regardless of its size.
589     //
590     //  (0,0)         (0,0)
591     //     0*    *2     0*       *2
592     //     TOP          TOP
593     //     3*    *1     3*       *1
594     //  4*       *1     4*     *6
595     //     MIDDLE         BOTTOM
596     //  6*       *5     7*     *5
597     //  6*    *8
598     //   BOTTOM
599     //  9*    *7
600     //
601
602     if ( mHighlightMesh && mHighlightMaterial && !mHighlightQuadList.empty() )
603     {
604       MeshData::VertexContainer vertices;
605       Dali::MeshData::FaceIndices faceIndices;
606
607       std::vector<QuadCoordinates>::iterator iter = mHighlightQuadList.begin();
608       std::vector<QuadCoordinates>::iterator endIter = mHighlightQuadList.end();
609
610       // vertex position defaults to (0 0 0)
611       MeshData::Vertex vertex;
612       // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
613       vertex.nZ = 1.0f;
614
615       for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
616       {
617         // Add each quad geometry (a sub-selection) to the mesh data.
618
619         // 0-----1
620         // |\    |
621         // | \ A |
622         // |  \  |
623         // | B \ |
624         // |    \|
625         // 2-----3
626
627         QuadCoordinates& quad = *iter;
628         // top-left (v+0)
629         vertex.x = quad.min.x;
630         vertex.y = quad.min.y;
631         vertices.push_back( vertex );
632
633         // top-right (v+1)
634         vertex.x = quad.max.x;
635         vertex.y = quad.min.y;
636         vertices.push_back( vertex );
637
638         // bottom-left (v+2)
639         vertex.x = quad.min.x;
640         vertex.y = quad.max.y;
641         vertices.push_back( vertex );
642
643         // bottom-right (v+3)
644         vertex.x = quad.max.x;
645         vertex.y = quad.max.y;
646         vertices.push_back( vertex );
647
648         // triangle A (3, 1, 0)
649         faceIndices.push_back( v + 3 );
650         faceIndices.push_back( v + 1 );
651         faceIndices.push_back( v );
652
653         // triangle B (0, 2, 3)
654         faceIndices.push_back( v );
655         faceIndices.push_back( v + 2 );
656         faceIndices.push_back( v + 3 );
657
658         mHighlightMeshData.SetFaceIndices( faceIndices );
659       }
660
661       BoneContainer bones(0); // passed empty as bones not required
662       mHighlightMeshData.SetData( vertices, faceIndices, bones, mHighlightMaterial );
663       mHighlightMesh.UpdateMeshData( mHighlightMeshData );
664     }
665   }
666
667   void OnTap( Actor actor, const TapGesture& tap )
668   {
669     if( actor == mGrabHandle )
670     {
671       // TODO
672     }
673   }
674
675   void OnPan( Actor actor, const PanGesture& gesture )
676   {
677     if( actor == mGrabArea )
678     {
679       if( Gesture::Started == gesture.state )
680       {
681         mGrabDisplacementX = mGrabDisplacementY = 0;
682       }
683
684       mGrabDisplacementX += gesture.displacement.x;
685       mGrabDisplacementY += gesture.displacement.y;
686
687       const float x = mCursor[PRIMARY_CURSOR].position.x + mGrabDisplacementX;
688       const float y = mCursor[PRIMARY_CURSOR].position.y + mCursor[PRIMARY_CURSOR].lineHeight*0.5f + mGrabDisplacementY;
689
690       if( Gesture::Started    == gesture.state ||
691           Gesture::Continuing == gesture.state )
692       {
693         mObserver.GrabHandleEvent( GRAB_HANDLE_PRESSED, x, y );
694       }
695       else if( Gesture::Finished  == gesture.state ||
696                Gesture::Cancelled == gesture.state )
697       {
698         mObserver.GrabHandleEvent( GRAB_HANDLE_RELEASED, x, y );
699       }
700     }
701   }
702
703   bool OnHandleOneTouched( Actor actor, const TouchEvent& touch )
704   {
705     // TODO
706     return false;
707   }
708
709   bool OnHandleTwoTouched( Actor actor, const TouchEvent& touch )
710   {
711     // TODO
712     return false;
713   }
714
715   // Popup
716
717   float AlternatePopUpPositionRelativeToCursor()
718   {
719     float alternativePosition=0.0f;;
720
721     if ( mPrimaryCursor ) // Secondary cursor not used for paste
722     {
723       Cursor cursor = PRIMARY_CURSOR;
724       alternativePosition = mCursor[cursor].position.y;
725     }
726
727     const float popupHeight = 120.0f; // todo Set as a MaxSize Property in Control or retrieve from CopyPastePopup class.
728
729     if( mActiveGrabHandle )
730     {
731       // If grab handle enabled then position pop-up below the grab handle.
732       const Vector2 grabHandleSize( 59.0f, 56.0f ); // todo
733       const float BOTTOM_HANDLE_BOTTOM_OFFSET = 1.5; //todo Should be a property
734       alternativePosition +=  grabHandleSize.height  + popupHeight + BOTTOM_HANDLE_BOTTOM_OFFSET ;
735     }
736     else
737     {
738       alternativePosition += popupHeight;
739     }
740
741     return alternativePosition;
742   }
743
744   void PopUpLeavesVerticalBoundary( PropertyNotification& source )
745   {
746     float alternativeYPosition=0.0f;
747   // todo
748   //  if( mHighlightMeshActor ) // Text Selection mode
749   //  {
750   //    alternativePosition = AlternatePopUpPositionRelativeToSelectionHandles();
751   //  }
752   //  else // Not in Text Selection mode
753   //  {
754     // if can't be positioned above, then position below row.
755     alternativeYPosition = AlternatePopUpPositionRelativeToCursor();
756    // }
757     mCopyPastePopup.SetY( alternativeYPosition );
758   }
759
760
761   void SetUpPopUpPositionNotifications( )
762   {
763     // Note Property notifications ignore any set anchor point so conditions must allow for this.  Default is Top Left.
764
765     // Exceeding vertical boundary
766
767     Vector4 worldCoordinatesBoundingBox;
768     LocalToWorldCoordinatesBoundingBox( mBoundingBox, worldCoordinatesBoundingBox );
769
770     float popupHeight = mCopyPastePopup.GetRelayoutSize( Dimension::HEIGHT);
771
772     PropertyNotification verticalExceedNotification = mCopyPastePopup.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
773                                                       OutsideCondition( worldCoordinatesBoundingBox.y + popupHeight/2,
774                                                                         worldCoordinatesBoundingBox.w - popupHeight/2 ) );
775
776     verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary );
777   }
778
779   void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, Vector3& popupSize, Vector3 anchorPoint, Actor& parent, Rect<int>& boundingBox )
780   {
781     DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() )
782
783     // Parent must already by added to Stage for these Get calls to work
784     Vector3 parentAnchorPoint = parent.GetCurrentAnchorPoint();
785     Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize()*parentAnchorPoint;
786     Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition;  // Parent World position plus popup local position gives World Position
787     Vector3 popupDistanceFromAnchorPoint = popupSize*anchorPoint;
788
789     // Bounding rectangle is supplied as screen coordinates, bounding will be done in world coordinates.
790     Vector4 boundingRectangleWorld;
791     LocalToWorldCoordinatesBoundingBox( boundingBox, boundingRectangleWorld );
792
793     // Calculate distance to move popup (in local space) so fits within the boundary
794     float xOffSetToKeepWithinBounds = 0.0f;
795     if( popupWorldPosition.x - popupDistanceFromAnchorPoint.x < boundingRectangleWorld.x )
796     {
797       xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x );
798     }
799     else if ( popupWorldPosition.x +  popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z )
800     {
801       xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x +  popupDistanceFromAnchorPoint.x );
802     }
803
804     // Ensure initial display of Popup is in alternative position if can not fit above. As Property notification will be a frame behind.
805     if ( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y )
806     {
807       requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor();
808     }
809
810     requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds;
811   }
812
813   Internal::Control&  mTextControlParent;
814   Observer&           mObserver;
815
816   TapGestureDetector  mTapDetector;
817   PanGestureDetector  mPanGestureDetector;
818   Timer               mCursorBlinkTimer;          ///< Timer to signal cursor to blink
819
820   Layer               mActiveLayer;               ///< Layer for active handles and alike that ensures they are above all else.
821   ImageActor          mPrimaryCursor;
822   ImageActor          mSecondaryCursor;
823   ImageActor          mGrabHandle;
824   Actor               mGrabArea;
825   MeshActor           mHighlightMeshActor;        ///< Mesh Actor to display highlight
826   TextSelectionPopup  mCopyPastePopup;
827
828   Image               mCursorImage;
829   Image               mGrabHandleImage;
830   Mesh                mHighlightMesh;             ///< Mesh for highlight
831   MeshData            mHighlightMeshData;         ///< Mesh Data for highlight
832   Material            mHighlightMaterial;         ///< Material used for highlight
833
834   CursorImpl          mCursor[CURSOR_COUNT];
835   SelectionHandleImpl mSelectionHandle[SELECTION_HANDLE_COUNT];
836   QuadContainer       mHighlightQuadList;         ///< Sub-selections that combine to create the complete selection highlight
837
838   Rect<int>           mBoundingBox;
839   Vector4             mHighlightColor;            ///< Color of the highlight
840
841   unsigned int        mActiveCursor;
842   unsigned int        mCursorBlinkInterval;
843   float               mCursorBlinkDuration;
844   float               mGrabDisplacementX;
845   float               mGrabDisplacementY;
846
847   bool                mActiveGrabHandle       : 1;
848   bool                mActiveSelection        : 1;
849   bool                mActiveCopyPastePopup   : 1;
850   bool                mCursorBlinkStatus      : 1; ///< Flag to switch between blink on and blink off.
851   bool                mPrimaryCursorVisible   : 1; ///< Whether the primary cursor is visible.
852   bool                mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible.
853 };
854
855 DecoratorPtr Decorator::New( Internal::Control& parent, Observer& observer )
856 {
857   return DecoratorPtr( new Decorator(parent, observer) );
858 }
859
860 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
861 {
862   mImpl->mBoundingBox = boundingBox;
863 }
864
865 const Rect<int>& Decorator::GetBoundingBox() const
866 {
867   return mImpl->mBoundingBox;
868 }
869
870 void Decorator::Relayout( const Vector2& size )
871 {
872   mImpl->Relayout( size );
873 }
874
875 void Decorator::UpdatePositions( const Vector2& scrollOffset )
876 {
877   mImpl->UpdatePositions( scrollOffset );
878 }
879
880 /** Cursor **/
881
882 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
883 {
884   mImpl->mActiveCursor = activeCursor;
885 }
886
887 unsigned int Decorator::GetActiveCursor() const
888 {
889   return mImpl->mActiveCursor;
890 }
891
892 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
893 {
894   // Adjust grab handle displacement
895   if( PRIMARY_CURSOR == cursor )
896   {
897     mImpl->mGrabDisplacementX -= x - mImpl->mCursor[cursor].position.x;
898     mImpl->mGrabDisplacementY -= y - mImpl->mCursor[cursor].position.y;
899   }
900
901   mImpl->mCursor[cursor].position.x = x;
902   mImpl->mCursor[cursor].position.y = y;
903   mImpl->mCursor[cursor].cursorHeight = cursorHeight;
904   mImpl->mCursor[cursor].lineHeight = lineHeight;
905 }
906
907 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
908 {
909   x = mImpl->mCursor[cursor].position.x;
910   y = mImpl->mCursor[cursor].position.y;
911   cursorHeight = mImpl->mCursor[cursor].cursorHeight;
912   lineHeight = mImpl->mCursor[cursor].lineHeight;
913 }
914
915 const Vector2& Decorator::GetPosition( Cursor cursor ) const
916 {
917   return mImpl->mCursor[cursor].position;
918 }
919
920 void Decorator::SetColor( Cursor cursor, const Dali::Vector4& color )
921 {
922   mImpl->mCursor[cursor].color = color;
923 }
924
925 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
926 {
927   return mImpl->mCursor[cursor].color;
928 }
929
930 void Decorator::StartCursorBlink()
931 {
932   if ( !mImpl->mCursorBlinkTimer )
933   {
934     mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
935     mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
936   }
937
938   if ( !mImpl->mCursorBlinkTimer.IsRunning() )
939   {
940     mImpl->mCursorBlinkTimer.Start();
941   }
942 }
943
944 void Decorator::StopCursorBlink()
945 {
946   if ( mImpl->mCursorBlinkTimer )
947   {
948     mImpl->mCursorBlinkTimer.Stop();
949   }
950 }
951
952 void Decorator::SetCursorBlinkInterval( float seconds )
953 {
954   mImpl->mCursorBlinkInterval = static_cast<unsigned int>( seconds * TO_MILLISECONDS ); // Convert to milliseconds
955 }
956
957 float Decorator::GetCursorBlinkInterval() const
958 {
959   return static_cast<float>( mImpl->mCursorBlinkInterval ) * TO_SECONDS;
960 }
961
962 void Decorator::SetCursorBlinkDuration( float seconds )
963 {
964   mImpl->mCursorBlinkDuration = seconds;
965 }
966
967 float Decorator::GetCursorBlinkDuration() const
968 {
969   return mImpl->mCursorBlinkDuration;
970 }
971
972 /** GrabHandle **/
973
974 void Decorator::SetGrabHandleActive( bool active )
975 {
976   mImpl->mActiveGrabHandle = active;
977 }
978
979 bool Decorator::IsGrabHandleActive() const
980 {
981   return mImpl->mActiveGrabHandle;
982 }
983
984 void Decorator::SetGrabHandleImage( Dali::Image image )
985 {
986   mImpl->mGrabHandleImage = image;
987 }
988
989 Dali::Image Decorator::GetGrabHandleImage() const
990 {
991   return mImpl->mGrabHandleImage;
992 }
993
994 /** Selection **/
995
996 void Decorator::SetSelectionActive( bool active )
997 {
998   mImpl->mActiveSelection = active;
999 }
1000
1001 bool Decorator::IsSelectionActive() const
1002 {
1003   return mImpl->mActiveSelection;
1004 }
1005
1006 void Decorator::SetPosition( SelectionHandle handle, float x, float y, float height )
1007 {
1008   mImpl->mSelectionHandle[handle].position.x = x;
1009   mImpl->mSelectionHandle[handle].position.y = y;
1010   mImpl->mSelectionHandle[handle].lineHeight = height;
1011 }
1012
1013 void Decorator::GetPosition( SelectionHandle handle, float& x, float& y, float& height ) const
1014 {
1015   x = mImpl->mSelectionHandle[handle].position.x;
1016   y = mImpl->mSelectionHandle[handle].position.y;
1017   height = mImpl->mSelectionHandle[handle].lineHeight;
1018 }
1019
1020 void Decorator::SetImage( SelectionHandle handle, SelectionHandleState state, Dali::Image image )
1021 {
1022   if( SELECTION_HANDLE_PRESSED == state )
1023   {
1024     mImpl->mSelectionHandle[handle].pressedImage = image;
1025   }
1026   else
1027   {
1028     mImpl->mSelectionHandle[handle].releasedImage = image;
1029   }
1030 }
1031
1032 Dali::Image Decorator::GetImage( SelectionHandle handle, SelectionHandleState state ) const
1033 {
1034   if( SELECTION_HANDLE_PRESSED == state )
1035   {
1036     return mImpl->mSelectionHandle[handle].pressedImage;
1037   }
1038
1039   return mImpl->mSelectionHandle[handle].releasedImage;
1040 }
1041
1042 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1043 {
1044   mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1045 }
1046
1047 void Decorator::ClearHighlights()
1048 {
1049   mImpl->mHighlightQuadList.clear();
1050 }
1051
1052 void Decorator::SetPopupActive( bool active )
1053 {
1054   mImpl->mActiveCopyPastePopup = active;
1055 }
1056
1057 bool Decorator::IsPopupActive() const
1058 {
1059   return mImpl->mActiveCopyPastePopup ;
1060 }
1061
1062 Decorator::~Decorator()
1063 {
1064   delete mImpl;
1065 }
1066
1067 Decorator::Decorator( Dali::Toolkit::Internal::Control& parent, Observer& observer )
1068 : mImpl( NULL )
1069 {
1070   mImpl = new Decorator::Impl( parent, observer );
1071 }
1072
1073 } // namespace Text
1074
1075 } // namespace Toolkit
1076
1077 } // namespace Dali