2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali-toolkit/internal/text/decorator/text-decorator.h>
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>
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>
53 #define DECORATOR_DEBUG
63 #ifdef DECORATOR_DEBUG
64 Integration::Log::Filter* gLogFilter( Integration::Log::Filter::New(Debug::NoLogging, false, "LOG_TEXT_DECORATOR") );
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" );
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 );
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;
88 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( -0.05f );
91 * structure to hold coordinates of each quad, which will make up the mesh.
93 struct QuadCoordinates
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
109 QuadCoordinates(float x1, float y1, float x2, float y2)
115 Dali::Vector2 min; ///< top-left (minimum) position of quad
116 Dali::Vector2 max; ///< bottom-right (maximum) position of quad
119 typedef std::vector<QuadCoordinates> QuadContainer;
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.
126 void LocalToWorldCoordinatesBoundingBox( const Dali::Rect<int>& boundingRectangle, Dali::Vector4& boundingBox )
128 // Convert to world coordinates and store as a Vector4 to be compatible with Property Notifications.
129 Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
131 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
132 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
134 boundingBox = Dali::Vector4( originX,
136 originX + boundingRectangle.width,
137 originY + boundingRectangle.height );
141 } // end of namespace
152 struct Decorator::Impl : public ConnectionTracker
157 : color( Dali::Color::WHITE ),
159 cursorHeight( 0.0f ),
170 struct SelectionHandleImpl
172 SelectionHandleImpl()
186 float lineHeight; ///< Not the handle height
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 )
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.
213 void Relayout( const Vector2& size )
215 // TODO - Remove this if nothing is active
218 // Show or hide the cursors
222 mPrimaryCursorVisible = ( mCursor[PRIMARY_CURSOR].position.x <= size.width ) && ( mCursor[PRIMARY_CURSOR].position.x >= 0.f );
223 if( mPrimaryCursorVisible )
225 mPrimaryCursor.SetPosition( mCursor[PRIMARY_CURSOR].position.x,
226 mCursor[PRIMARY_CURSOR].position.y );
227 mPrimaryCursor.SetSize( Size( 1.0f, mCursor[PRIMARY_CURSOR].cursorHeight ) );
229 mPrimaryCursor.SetVisible( mPrimaryCursorVisible );
231 if( mSecondaryCursor )
233 mSecondaryCursorVisible = ( mCursor[SECONDARY_CURSOR].position.x <= size.width ) && ( mCursor[SECONDARY_CURSOR].position.x >= 0.f );
234 if( mSecondaryCursorVisible )
236 mSecondaryCursor.SetPosition( mCursor[SECONDARY_CURSOR].position.x,
237 mCursor[SECONDARY_CURSOR].position.y );
238 mSecondaryCursor.SetSize( Size( 1.0f, mCursor[SECONDARY_CURSOR].cursorHeight ) );
240 mSecondaryCursor.SetVisible( mSecondaryCursorVisible );
243 // Show or hide the grab handle
244 if( mActiveGrabHandle )
246 const bool isVisible = ( mCursor[PRIMARY_CURSOR].position.x <= size.width ) && ( mCursor[PRIMARY_CURSOR].position.x >= 0.f );
254 mGrabHandle.SetPosition( mCursor[PRIMARY_CURSOR].position.x,
255 mCursor[PRIMARY_CURSOR].position.y + mCursor[PRIMARY_CURSOR].lineHeight );
257 mGrabHandle.SetVisible( isVisible );
259 else if( mGrabHandle )
261 UnparentAndReset( mGrabHandle );
264 // Show or hide the selection handles/highlight
265 if( mActiveSelection )
269 CreateSelectionHandles();
271 SelectionHandleImpl& primary = mSelectionHandle[ PRIMARY_SELECTION_HANDLE ];
272 primary.actor.SetPosition( primary.position.x,
273 primary.position.y + primary.lineHeight );
275 SelectionHandleImpl& secondary = mSelectionHandle[ SECONDARY_SELECTION_HANDLE ];
276 secondary.actor.SetPosition( secondary.position.x,
277 secondary.position.y + secondary.lineHeight );
284 UnparentAndReset( mSelectionHandle[ PRIMARY_SELECTION_HANDLE ].actor );
285 UnparentAndReset( mSelectionHandle[ SECONDARY_SELECTION_HANDLE ].actor );
286 UnparentAndReset( mHighlightMeshActor );
289 if ( mActiveCopyPastePopup )
291 if ( !mCopyPastePopup )
293 mCopyPastePopup = TextSelectionPopup::New();
294 #ifdef DECORATOR_DEBUG
295 mCopyPastePopup.SetName("mCopyPastePopup");
297 mCopyPastePopup.SetAnchorPoint( AnchorPoint::CENTER );
298 mCopyPastePopup.OnRelayoutSignal().Connect( this, &Decorator::Impl::PopUpRelayoutComplete ); // Position popup after size negotiation
299 mActiveLayer.Add ( mCopyPastePopup );
304 if ( mCopyPastePopup )
306 UnparentAndReset( mCopyPastePopup );
311 void UpdatePositions( const Vector2& scrollOffset )
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;
318 // TODO Highlight box??
321 void PopUpRelayoutComplete( Actor actor )
323 // Size negotiation for CopyPastePopup complete so can get the size and constrain position within bounding box.
325 mCopyPastePopup.OnRelayoutSignal().Disconnect( this, &Decorator::Impl::PopUpRelayoutComplete );
327 Vector3 popupPosition( mCursor[PRIMARY_CURSOR].position.x, mCursor[PRIMARY_CURSOR].position.y -100.0f , 0.0f); //todo 100 to be an offset Property
329 Vector3 popupSize = Vector3( mCopyPastePopup.GetRelayoutSize( Dimension::WIDTH ), mCopyPastePopup.GetRelayoutSize( Dimension::HEIGHT ), 0.0f );
331 GetConstrainedPopupPosition( popupPosition, popupSize, AnchorPoint::CENTER, mActiveLayer, mBoundingBox );
333 SetUpPopUpPositionNotifications();
335 mCopyPastePopup.SetPosition( popupPosition ); //todo grabhandle(cursor) or selection handle positions to be used
338 void CreateCursor( ImageActor& cursor )
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 );
345 // Add or Remove cursor(s) from parent
348 if( mActiveCursor == ACTIVE_CURSOR_NONE )
350 UnparentAndReset( mPrimaryCursor );
351 UnparentAndReset( mSecondaryCursor );
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 )
359 if ( !mPrimaryCursor )
361 CreateCursor( mPrimaryCursor );
362 #ifdef DECORATOR_DEBUG
363 mPrimaryCursor.SetName( "PrimaryCursorActor" );
365 mActiveLayer.Add( mPrimaryCursor );
369 if ( mActiveCursor == ACTIVE_CURSOR_BOTH )
371 if ( !mSecondaryCursor )
373 CreateCursor( mSecondaryCursor );
374 #ifdef DECORATOR_DEBUG
375 mSecondaryCursor.SetName( "SecondaryCursorActor" );
377 mActiveLayer.Add( mSecondaryCursor );
382 UnparentAndReset( mSecondaryCursor );
387 bool OnCursorBlinkTimerTick()
390 if ( mPrimaryCursor )
392 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
394 if ( mSecondaryCursor )
396 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
399 mCursorBlinkStatus = !mCursorBlinkStatus;
404 void SetupTouchEvents()
408 mTapDetector = TapGestureDetector::New();
409 mTapDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnTap );
412 if ( !mPanGestureDetector )
414 mPanGestureDetector = PanGestureDetector::New();
415 mPanGestureDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan );
419 void CreateActiveLayer()
423 Actor parent = mTextControlParent.Self();
425 mActiveLayer = Layer::New();
426 #ifdef DECORATOR_DEBUG
427 mActiveLayer.SetName ( "ActiveLayerActor" );
430 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER );
431 mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
432 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
434 parent.Add( mActiveLayer );
437 mActiveLayer.RaiseToTop();
440 void CreateGrabHandle()
444 if ( !mGrabHandleImage )
446 mGrabHandleImage = ResourceImage::New( DEFAULT_GRAB_HANDLE_IMAGE );
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 ) )
457 mGrabArea = Toolkit::CreateSolidColorActor( Vector4(0.0f, 0.0f, 0.0f, 0.0f), true, Color::RED, 1 );
458 mGrabArea.SetName( "GrabArea" );
462 mGrabArea = Actor::New();
463 mGrabArea.SetName( "GrabArea" );
466 mGrabArea = Actor::New();
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 );
475 mTapDetector.Attach( mGrabArea );
476 mPanGestureDetector.Attach( mGrabArea );
478 mActiveLayer.Add( mGrabHandle );
482 void CreateSelectionHandles()
484 SelectionHandleImpl& primary = mSelectionHandle[ PRIMARY_SELECTION_HANDLE ];
485 if ( !primary.actor )
487 if ( !primary.releasedImage )
489 primary.releasedImage = ResourceImage::New( DEFAULT_SELECTION_HANDLE_ONE );
492 primary.actor = ImageActor::New( primary.releasedImage );
493 #ifdef DECORATOR_DEBUG
494 primary.actor.SetName("SelectionHandleOne");
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;
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");
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 );
508 mTapDetector.Attach( primary.grabArea );
509 mPanGestureDetector.Attach( primary.grabArea );
510 primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched );
512 primary.actor.Add( primary.grabArea );
513 mActiveLayer.Add( primary.actor );
516 SelectionHandleImpl& secondary = mSelectionHandle[ SECONDARY_SELECTION_HANDLE ];
517 if ( !secondary.actor )
519 if ( !secondary.releasedImage )
521 secondary.releasedImage = ResourceImage::New( DEFAULT_SELECTION_HANDLE_TWO );
524 secondary.actor = ImageActor::New( secondary.releasedImage );
525 #ifdef DECORATOR_DEBUG
526 secondary.actor.SetName("SelectionHandleTwo");
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;
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");
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 );
540 mTapDetector.Attach( secondary.grabArea );
541 mPanGestureDetector.Attach( secondary.grabArea );
542 secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched );
544 secondary.actor.Add( secondary.grabArea );
545 mActiveLayer.Add( secondary.actor );
548 //SetUpHandlePropertyNotifications(); TODO
551 void CreateHighlight()
553 if ( !mHighlightMeshActor )
555 mHighlightMaterial = Material::New( "HighlightMaterial" );
556 mHighlightMaterial.SetDiffuseColor( mHighlightColor );
558 mHighlightMeshData.SetMaterial( mHighlightMaterial );
559 mHighlightMeshData.SetHasNormals( true );
561 mHighlightMesh = Mesh::New( mHighlightMeshData );
563 mHighlightMeshActor = MeshActor::New( mHighlightMesh );
564 #ifdef DECORATOR_DEBUG
565 mHighlightMeshActor.SetName( "HighlightMeshActor" );
567 mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
568 mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
570 Actor parent = mTextControlParent.Self();
571 parent.Add( mHighlightMeshActor );
575 void UpdateHighlight()
577 // Construct a Mesh with a texture to be used as the highlight 'box' for selected text
579 // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
581 // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ]
582 // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ]
583 // [ BOTTOM] [ MIDDLE ] [ MIDDLE ]
584 // [BOTTOM] [ MIDDLE ]
587 // Each quad is created as 2 triangles.
588 // Middle is just 1 quad regardless of its size.
602 if ( mHighlightMesh && mHighlightMaterial && !mHighlightQuadList.empty() )
604 MeshData::VertexContainer vertices;
605 Dali::MeshData::FaceIndices faceIndices;
607 std::vector<QuadCoordinates>::iterator iter = mHighlightQuadList.begin();
608 std::vector<QuadCoordinates>::iterator endIter = mHighlightQuadList.end();
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.
615 for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
617 // Add each quad geometry (a sub-selection) to the mesh data.
627 QuadCoordinates& quad = *iter;
629 vertex.x = quad.min.x;
630 vertex.y = quad.min.y;
631 vertices.push_back( vertex );
634 vertex.x = quad.max.x;
635 vertex.y = quad.min.y;
636 vertices.push_back( vertex );
639 vertex.x = quad.min.x;
640 vertex.y = quad.max.y;
641 vertices.push_back( vertex );
643 // bottom-right (v+3)
644 vertex.x = quad.max.x;
645 vertex.y = quad.max.y;
646 vertices.push_back( vertex );
648 // triangle A (3, 1, 0)
649 faceIndices.push_back( v + 3 );
650 faceIndices.push_back( v + 1 );
651 faceIndices.push_back( v );
653 // triangle B (0, 2, 3)
654 faceIndices.push_back( v );
655 faceIndices.push_back( v + 2 );
656 faceIndices.push_back( v + 3 );
658 mHighlightMeshData.SetFaceIndices( faceIndices );
661 BoneContainer bones(0); // passed empty as bones not required
662 mHighlightMeshData.SetData( vertices, faceIndices, bones, mHighlightMaterial );
663 mHighlightMesh.UpdateMeshData( mHighlightMeshData );
667 void OnTap( Actor actor, const TapGesture& tap )
669 if( actor == mGrabHandle )
675 void OnPan( Actor actor, const PanGesture& gesture )
677 if( actor == mGrabArea )
679 if( Gesture::Started == gesture.state )
681 mGrabDisplacementX = mGrabDisplacementY = 0;
684 mGrabDisplacementX += gesture.displacement.x;
685 mGrabDisplacementY += gesture.displacement.y;
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;
690 if( Gesture::Started == gesture.state ||
691 Gesture::Continuing == gesture.state )
693 mObserver.GrabHandleEvent( GRAB_HANDLE_PRESSED, x, y );
695 else if( Gesture::Finished == gesture.state ||
696 Gesture::Cancelled == gesture.state )
698 mObserver.GrabHandleEvent( GRAB_HANDLE_RELEASED, x, y );
703 bool OnHandleOneTouched( Actor actor, const TouchEvent& touch )
709 bool OnHandleTwoTouched( Actor actor, const TouchEvent& touch )
717 float AlternatePopUpPositionRelativeToCursor()
719 float alternativePosition=0.0f;;
721 if ( mPrimaryCursor ) // Secondary cursor not used for paste
723 Cursor cursor = PRIMARY_CURSOR;
724 alternativePosition = mCursor[cursor].position.y;
727 const float popupHeight = 120.0f; // todo Set as a MaxSize Property in Control or retrieve from CopyPastePopup class.
729 if( mActiveGrabHandle )
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 ;
738 alternativePosition += popupHeight;
741 return alternativePosition;
744 void PopUpLeavesVerticalBoundary( PropertyNotification& source )
746 float alternativeYPosition=0.0f;
748 // if( mHighlightMeshActor ) // Text Selection mode
750 // alternativePosition = AlternatePopUpPositionRelativeToSelectionHandles();
752 // else // Not in Text Selection mode
754 // if can't be positioned above, then position below row.
755 alternativeYPosition = AlternatePopUpPositionRelativeToCursor();
757 mCopyPastePopup.SetY( alternativeYPosition );
761 void SetUpPopUpPositionNotifications( )
763 // Note Property notifications ignore any set anchor point so conditions must allow for this. Default is Top Left.
765 // Exceeding vertical boundary
767 Vector4 worldCoordinatesBoundingBox;
768 LocalToWorldCoordinatesBoundingBox( mBoundingBox, worldCoordinatesBoundingBox );
770 float popupHeight = mCopyPastePopup.GetRelayoutSize( Dimension::HEIGHT);
772 PropertyNotification verticalExceedNotification = mCopyPastePopup.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
773 OutsideCondition( worldCoordinatesBoundingBox.y + popupHeight/2,
774 worldCoordinatesBoundingBox.w - popupHeight/2 ) );
776 verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary );
779 void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, Vector3& popupSize, Vector3 anchorPoint, Actor& parent, Rect<int>& boundingBox )
781 DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() )
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;
789 // Bounding rectangle is supplied as screen coordinates, bounding will be done in world coordinates.
790 Vector4 boundingRectangleWorld;
791 LocalToWorldCoordinatesBoundingBox( boundingBox, boundingRectangleWorld );
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 )
797 xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x );
799 else if ( popupWorldPosition.x + popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z )
801 xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x + popupDistanceFromAnchorPoint.x );
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 )
807 requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor();
810 requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds;
813 Internal::Control& mTextControlParent;
816 TapGestureDetector mTapDetector;
817 PanGestureDetector mPanGestureDetector;
818 Timer mCursorBlinkTimer; ///< Timer to signal cursor to blink
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;
825 MeshActor mHighlightMeshActor; ///< Mesh Actor to display highlight
826 TextSelectionPopup mCopyPastePopup;
829 Image mGrabHandleImage;
830 Mesh mHighlightMesh; ///< Mesh for highlight
831 MeshData mHighlightMeshData; ///< Mesh Data for highlight
832 Material mHighlightMaterial; ///< Material used for highlight
834 CursorImpl mCursor[CURSOR_COUNT];
835 SelectionHandleImpl mSelectionHandle[SELECTION_HANDLE_COUNT];
836 QuadContainer mHighlightQuadList; ///< Sub-selections that combine to create the complete selection highlight
838 Rect<int> mBoundingBox;
839 Vector4 mHighlightColor; ///< Color of the highlight
841 unsigned int mActiveCursor;
842 unsigned int mCursorBlinkInterval;
843 float mCursorBlinkDuration;
844 float mGrabDisplacementX;
845 float mGrabDisplacementY;
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.
855 DecoratorPtr Decorator::New( Internal::Control& parent, Observer& observer )
857 return DecoratorPtr( new Decorator(parent, observer) );
860 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
862 mImpl->mBoundingBox = boundingBox;
865 const Rect<int>& Decorator::GetBoundingBox() const
867 return mImpl->mBoundingBox;
870 void Decorator::Relayout( const Vector2& size )
872 mImpl->Relayout( size );
875 void Decorator::UpdatePositions( const Vector2& scrollOffset )
877 mImpl->UpdatePositions( scrollOffset );
882 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
884 mImpl->mActiveCursor = activeCursor;
887 unsigned int Decorator::GetActiveCursor() const
889 return mImpl->mActiveCursor;
892 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
894 // Adjust grab handle displacement
895 if( PRIMARY_CURSOR == cursor )
897 mImpl->mGrabDisplacementX -= x - mImpl->mCursor[cursor].position.x;
898 mImpl->mGrabDisplacementY -= y - mImpl->mCursor[cursor].position.y;
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;
907 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
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;
915 const Vector2& Decorator::GetPosition( Cursor cursor ) const
917 return mImpl->mCursor[cursor].position;
920 void Decorator::SetColor( Cursor cursor, const Dali::Vector4& color )
922 mImpl->mCursor[cursor].color = color;
925 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
927 return mImpl->mCursor[cursor].color;
930 void Decorator::StartCursorBlink()
932 if ( !mImpl->mCursorBlinkTimer )
934 mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
935 mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
938 if ( !mImpl->mCursorBlinkTimer.IsRunning() )
940 mImpl->mCursorBlinkTimer.Start();
944 void Decorator::StopCursorBlink()
946 if ( mImpl->mCursorBlinkTimer )
948 mImpl->mCursorBlinkTimer.Stop();
952 void Decorator::SetCursorBlinkInterval( float seconds )
954 mImpl->mCursorBlinkInterval = static_cast<unsigned int>( seconds * TO_MILLISECONDS ); // Convert to milliseconds
957 float Decorator::GetCursorBlinkInterval() const
959 return static_cast<float>( mImpl->mCursorBlinkInterval ) * TO_SECONDS;
962 void Decorator::SetCursorBlinkDuration( float seconds )
964 mImpl->mCursorBlinkDuration = seconds;
967 float Decorator::GetCursorBlinkDuration() const
969 return mImpl->mCursorBlinkDuration;
974 void Decorator::SetGrabHandleActive( bool active )
976 mImpl->mActiveGrabHandle = active;
979 bool Decorator::IsGrabHandleActive() const
981 return mImpl->mActiveGrabHandle;
984 void Decorator::SetGrabHandleImage( Dali::Image image )
986 mImpl->mGrabHandleImage = image;
989 Dali::Image Decorator::GetGrabHandleImage() const
991 return mImpl->mGrabHandleImage;
996 void Decorator::SetSelectionActive( bool active )
998 mImpl->mActiveSelection = active;
1001 bool Decorator::IsSelectionActive() const
1003 return mImpl->mActiveSelection;
1006 void Decorator::SetPosition( SelectionHandle handle, float x, float y, float height )
1008 mImpl->mSelectionHandle[handle].position.x = x;
1009 mImpl->mSelectionHandle[handle].position.y = y;
1010 mImpl->mSelectionHandle[handle].lineHeight = height;
1013 void Decorator::GetPosition( SelectionHandle handle, float& x, float& y, float& height ) const
1015 x = mImpl->mSelectionHandle[handle].position.x;
1016 y = mImpl->mSelectionHandle[handle].position.y;
1017 height = mImpl->mSelectionHandle[handle].lineHeight;
1020 void Decorator::SetImage( SelectionHandle handle, SelectionHandleState state, Dali::Image image )
1022 if( SELECTION_HANDLE_PRESSED == state )
1024 mImpl->mSelectionHandle[handle].pressedImage = image;
1028 mImpl->mSelectionHandle[handle].releasedImage = image;
1032 Dali::Image Decorator::GetImage( SelectionHandle handle, SelectionHandleState state ) const
1034 if( SELECTION_HANDLE_PRESSED == state )
1036 return mImpl->mSelectionHandle[handle].pressedImage;
1039 return mImpl->mSelectionHandle[handle].releasedImage;
1042 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1044 mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1047 void Decorator::ClearHighlights()
1049 mImpl->mHighlightQuadList.clear();
1052 void Decorator::SetPopupActive( bool active )
1054 mImpl->mActiveCopyPastePopup = active;
1057 bool Decorator::IsPopupActive() const
1059 return mImpl->mActiveCopyPastePopup ;
1062 Decorator::~Decorator()
1067 Decorator::Decorator( Dali::Toolkit::Internal::Control& parent, Observer& observer )
1070 mImpl = new Decorator::Impl( parent, observer );
1075 } // namespace Toolkit