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>
24 #include <dali/public-api/adaptor-framework/timer.h>
25 #include <dali/public-api/actors/layer.h>
26 #include <dali/public-api/common/stage.h>
27 #include <dali/public-api/events/touch-event.h>
28 #include <dali/public-api/events/pan-gesture.h>
29 #include <dali/public-api/images/resource-image.h>
30 #include <dali/public-api/object/property-notification.h>
32 #include <dali/devel-api/rendering/geometry.h>
33 #include <dali/devel-api/rendering/renderer.h>
36 #include <dali-toolkit/public-api/controls/control-depth-index-ranges.h>
37 #include <dali-toolkit/public-api/controls/default-controls/solid-color-actor.h>
40 #define DECORATOR_DEBUG
44 #define MAKE_SHADER(A)#A
48 const char* VERTEX_SHADER = MAKE_SHADER(
49 attribute mediump vec2 aPosition;
50 uniform mediump mat4 uMvpMatrix;
51 uniform mediump vec3 uSize;
55 mediump vec4 position = vec4( aPosition, 0.0, 1.0 );
56 position.xyz *= uSize;
57 gl_Position = uMvpMatrix * position;
61 const char* FRAGMENT_SHADER = MAKE_SHADER(
62 uniform lowp vec4 uColor;
66 gl_FragColor = uColor;
77 #ifdef DECORATOR_DEBUG
78 Integration::Log::Filter* gLogFilter( Integration::Log::Filter::New(Debug::NoLogging, false, "LOG_TEXT_DECORATOR") );
88 const Dali::Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.25f, 1.5f, 1.0f );
89 const Dali::Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.25f, 1.5f, 1.0f );
91 const Dali::Vector4 LIGHT_BLUE( 0.75f, 0.96f, 1.f, 1.f ); // The text highlight color. TODO: due some problems, maybe with the blending function in the text clipping, the color is fully opaque.
93 const Dali::Vector4 HANDLE_COLOR( 0.0f, (183.0f / 255.0f), (229.0f / 255.0f), 1.0f );
95 const unsigned int CURSOR_BLINK_INTERVAL = 500u; // Cursor blink interval
96 const float TO_MILLISECONDS = 1000.f;
97 const float TO_SECONDS = 1.f / TO_MILLISECONDS;
99 const unsigned int SCROLL_TICK_INTERVAL = 50u;
101 const float SCROLL_THRESHOLD = 10.f;
102 const float SCROLL_SPEED = 300.f;
103 const float SCROLL_DISTANCE = SCROLL_SPEED * SCROLL_TICK_INTERVAL * TO_SECONDS;
105 const float CURSOR_WIDTH = 1.f;
108 * structure to hold coordinates of each quad, which will make up the mesh.
110 struct QuadCoordinates
113 * Default constructor
121 * @param[in] x1 left co-ordinate
122 * @param[in] y1 top co-ordinate
123 * @param[in] x2 right co-ordinate
124 * @param[in] y2 bottom co-ordinate
126 QuadCoordinates(float x1, float y1, float x2, float y2)
132 Dali::Vector2 min; ///< top-left (minimum) position of quad
133 Dali::Vector2 max; ///< bottom-right (maximum) position of quad
136 typedef std::vector<QuadCoordinates> QuadContainer;
139 * @brief Takes a bounding rectangle in the local coordinates of an actor and returns the world coordinates Bounding Box.
140 * @param[in] boundingRectangle local bounding
141 * @param[out] Vector4 World coordinate bounding Box.
143 void LocalToWorldCoordinatesBoundingBox( const Dali::Rect<int>& boundingRectangle, Dali::Vector4& boundingBox )
145 // Convert to world coordinates and store as a Vector4 to be compatible with Property Notifications.
146 Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
148 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
149 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
151 boundingBox = Dali::Vector4( originX,
153 originX + boundingRectangle.width,
154 originY + boundingRectangle.height );
157 void WorldToLocalCoordinatesBoundingBox( const Dali::Vector4& boundingBox, Dali::Rect<int>& boundingRectangle )
159 // Convert to local coordinates and store as a Dali::Rect.
160 Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
162 boundingRectangle.x = boundingBox.x + 0.5f * stageSize.width;
163 boundingRectangle.y = boundingBox.y + 0.5f * stageSize.height;
164 boundingRectangle.width = boundingBox.z - boundingBox.x;
165 boundingRectangle.height = boundingBox.w - boundingBox.y;
168 } // end of namespace
179 struct Decorator::Impl : public ConnectionTracker
193 : color( Dali::Color::BLACK ),
195 cursorHeight( 0.0f ),
212 grabDisplacementX( 0.f ),
213 grabDisplacementY( 0.f ),
217 verticallyFlippedPreferred( false ),
218 horizontallyFlipped( false ),
219 verticallyFlipped( false )
225 ImageActor markerActor;
229 float lineHeight; ///< Not the handle height
230 float grabDisplacementX;
231 float grabDisplacementY;
235 bool verticallyFlippedPreferred : 1; ///< Whether the handle is preferred to be vertically flipped.
236 bool horizontallyFlipped : 1; ///< Whether the handle has been horizontally flipped.
237 bool verticallyFlipped : 1; ///< Whether the handle has been vertically flipped.
247 TextSelectionPopup actor;
251 Impl( ControllerInterface& controller,
252 TextSelectionPopupCallbackInterface& callbackInterface )
253 : mController( controller ),
254 mEnabledPopupButtons( TextSelectionPopup::NONE ),
255 mTextSelectionPopupCallbackInterface( callbackInterface ),
256 mHandleColor( HANDLE_COLOR ),
258 mHighlightColor( LIGHT_BLUE ),
259 mHighlightPosition( Vector2::ZERO ),
260 mActiveCursor( ACTIVE_CURSOR_NONE ),
261 mCursorBlinkInterval( CURSOR_BLINK_INTERVAL ),
262 mCursorBlinkDuration( 0.0f ),
263 mCursorWidth( CURSOR_WIDTH ),
264 mHandleScrolling( HANDLE_TYPE_COUNT ),
265 mScrollDirection( SCROLL_NONE ),
266 mScrollThreshold( SCROLL_THRESHOLD ),
267 mScrollSpeed( SCROLL_SPEED ),
268 mScrollDistance( SCROLL_DISTANCE ),
270 mActiveCopyPastePopup( false ),
271 mPopupSetNewPosition( true ),
272 mCursorBlinkStatus( true ),
273 mDelayCursorBlink( false ),
274 mPrimaryCursorVisible( false ),
275 mSecondaryCursorVisible( false ),
276 mFlipSelectionHandlesOnCross( false ),
277 mFlipLeftSelectionHandleDirection( false ),
278 mFlipRightSelectionHandleDirection( false ),
279 mHandlePanning( false ),
280 mHandleCurrentCrossed( false ),
281 mHandlePreviousCrossed( false ),
282 mNotifyEndOfScroll( false )
284 mQuadVertexFormat[ "aPosition" ] = Property::VECTOR2;
285 mQuadIndexFormat[ "indices" ] = Property::INTEGER;
286 mHighlightMaterial = Material::New( Shader::New( VERTEX_SHADER, FRAGMENT_SHADER ) );
292 * Relayout of the decorations owned by the decorator.
293 * @param[in] size The Size of the UI control the decorator is adding it's decorations to.
295 void Relayout( const Vector2& size )
297 // TODO - Remove this if nothing is active
300 // Show or hide the cursors
305 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
306 mPrimaryCursorVisible = ( cursor.position.x + mCursorWidth <= size.width ) && ( cursor.position.x >= 0.f );
307 if( mPrimaryCursorVisible )
309 mPrimaryCursor.SetPosition( cursor.position.x,
311 mPrimaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) );
313 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
315 if( mSecondaryCursor )
317 const CursorImpl& cursor = mCursor[SECONDARY_CURSOR];
318 mSecondaryCursorVisible = ( cursor.position.x + mCursorWidth <= size.width ) && ( cursor.position.x >= 0.f );
319 if( mSecondaryCursorVisible )
321 mSecondaryCursor.SetPosition( cursor.position.x,
323 mSecondaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) );
325 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
328 // Show or hide the grab handle
329 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
330 if( grabHandle.active )
332 const bool isVisible = ( grabHandle.position.x + floor( 0.5f * mCursorWidth ) <= size.width ) && ( grabHandle.position.x >= 0.f );
338 // Sets the grab handle position and calculate if it needs to be vertically flipped if it exceeds the boundary box.
339 SetGrabHandlePosition();
341 // Sets the grab handle image according if it's pressed, flipped, etc.
342 SetHandleImage( GRAB_HANDLE );
345 if( grabHandle.actor )
347 grabHandle.actor.SetVisible( isVisible );
350 else if( grabHandle.actor )
352 grabHandle.actor.Unparent();
355 // Show or hide the selection handles/highlight
356 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
357 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
358 if( primary.active || secondary.active )
360 const bool isPrimaryVisible = ( primary.position.x <= size.width ) && ( primary.position.x >= 0.f );
361 const bool isSecondaryVisible = ( secondary.position.x <= size.width ) && ( secondary.position.x >= 0.f );
363 if( isPrimaryVisible || isSecondaryVisible )
365 CreateSelectionHandles();
367 if( isPrimaryVisible )
369 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
371 // Sets the primary handle image according if it's pressed, flipped, etc.
372 SetHandleImage( LEFT_SELECTION_HANDLE );
374 SetSelectionHandleMarkerSize( primary );
377 if( isSecondaryVisible )
379 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
381 // Sets the secondary handle image according if it's pressed, flipped, etc.
382 SetHandleImage( RIGHT_SELECTION_HANDLE );
384 SetSelectionHandleMarkerSize( secondary );
390 primary.actor.SetVisible( isPrimaryVisible );
392 if( secondary.actor )
394 secondary.actor.SetVisible( isSecondaryVisible );
404 primary.actor.Unparent();
406 if( secondary.actor )
408 secondary.actor.Unparent();
410 if( mHighlightActor )
412 mHighlightActor.Unparent();
416 if( mActiveCopyPastePopup )
422 if( mCopyPastePopup.actor )
424 mCopyPastePopup.actor.HidePopup();
425 mPopupSetNewPosition = true;
430 void UpdatePositions( const Vector2& scrollOffset )
432 mCursor[PRIMARY_CURSOR].position += scrollOffset;
433 mCursor[SECONDARY_CURSOR].position += scrollOffset;
434 mHandle[ GRAB_HANDLE ].position += scrollOffset;
435 mHandle[ LEFT_SELECTION_HANDLE ].position += scrollOffset;
436 mHandle[ RIGHT_SELECTION_HANDLE ].position += scrollOffset;
437 mHighlightPosition += scrollOffset;
442 if ( !mCopyPastePopup.actor )
447 if( !mCopyPastePopup.actor.GetParent() )
449 mActiveLayer.Add( mCopyPastePopup.actor );
452 mCopyPastePopup.actor.RaiseAbove( mActiveLayer );
453 mCopyPastePopup.actor.ShowPopup();
456 void DeterminePositionPopup()
458 if( !mActiveCopyPastePopup )
463 // Retrieves the popup's size after relayout.
464 const Vector3 popupSize = Vector3( mCopyPastePopup.actor.GetRelayoutSize( Dimension::WIDTH ), mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT ), 0.0f );
466 if( mPopupSetNewPosition )
468 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
469 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
470 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
471 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
473 if( primaryHandle.active || secondaryHandle.active )
475 // Calculates the popup's position if selection handles are active.
476 const float minHandleXPosition = std::min( primaryHandle.position.x, secondaryHandle.position.x );
477 const float maxHandleXPosition = std::max( primaryHandle.position.x, secondaryHandle.position.x );
478 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
480 mCopyPastePopup.position.x = minHandleXPosition + ( ( maxHandleXPosition - minHandleXPosition ) * 0.5f );
481 mCopyPastePopup.position.y = -0.5f * popupSize.height - maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
485 // Calculates the popup's position if the grab handle is active.
486 mCopyPastePopup.position = Vector3( cursor.position.x, -0.5f * popupSize.height - grabHandle.size.height + cursor.position.y, 0.0f );
490 // Checks if there is enough space above the text control. If not it places the popup under it.
491 GetConstrainedPopupPosition( mCopyPastePopup.position, popupSize * AnchorPoint::CENTER, mActiveLayer, mBoundingBox );
493 SetUpPopupPositionNotifications();
495 mCopyPastePopup.actor.SetPosition( mCopyPastePopup.position );
496 mPopupSetNewPosition = false;
499 void PopupRelayoutComplete( Actor actor )
501 // Size negotiation for CopyPastePopup complete so can get the size and constrain position within bounding box.
503 DeterminePositionPopup();
506 void CreateCursor( ImageActor& cursor, const Vector4& color )
508 cursor = CreateSolidColorActor( color );
509 cursor.SetSortModifier( DECORATION_DEPTH_INDEX );
510 cursor.SetParentOrigin( ParentOrigin::TOP_LEFT ); // Need to set the default parent origin as CreateSolidColorActor() sets a different one.
511 cursor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
514 // Add or Remove cursor(s) from parent
517 if( mActiveCursor == ACTIVE_CURSOR_NONE )
521 mPrimaryCursor.Unparent();
523 if( mSecondaryCursor )
525 mSecondaryCursor.Unparent();
530 // Create Primary and or Secondary Cursor(s) if active and add to parent
531 if ( mActiveCursor == ACTIVE_CURSOR_PRIMARY ||
532 mActiveCursor == ACTIVE_CURSOR_BOTH )
534 if ( !mPrimaryCursor )
536 CreateCursor( mPrimaryCursor, mCursor[PRIMARY_CURSOR].color );
537 #ifdef DECORATOR_DEBUG
538 mPrimaryCursor.SetName( "PrimaryCursorActor" );
542 if( !mPrimaryCursor.GetParent() )
544 mActiveLayer.Add( mPrimaryCursor );
548 if ( mActiveCursor == ACTIVE_CURSOR_BOTH )
550 if ( !mSecondaryCursor )
552 CreateCursor( mSecondaryCursor, mCursor[SECONDARY_CURSOR].color );
553 #ifdef DECORATOR_DEBUG
554 mSecondaryCursor.SetName( "SecondaryCursorActor" );
558 if( !mSecondaryCursor.GetParent() )
560 mActiveLayer.Add( mSecondaryCursor );
565 if( mSecondaryCursor )
567 mSecondaryCursor.Unparent();
573 bool OnCursorBlinkTimerTick()
575 if( !mDelayCursorBlink )
578 if ( mPrimaryCursor )
580 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
582 if ( mSecondaryCursor )
584 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
587 mCursorBlinkStatus = !mCursorBlinkStatus;
592 mDelayCursorBlink = false;
598 void SetupTouchEvents()
600 mTapDetector = TapGestureDetector::New();
601 mTapDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnTap );
603 mPanGestureDetector = PanGestureDetector::New();
604 mPanGestureDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan );
607 void CreateActiveLayer()
611 mActiveLayer = Layer::New();
612 #ifdef DECORATOR_DEBUG
613 mActiveLayer.SetName ( "ActiveLayerActor" );
616 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER );
617 mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
618 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
620 // Add the active layer telling the controller it doesn't need clipping.
621 mController.AddDecoration( mActiveLayer, false );
624 mActiveLayer.RaiseToTop();
627 void SetSelectionHandleMarkerSize( HandleImpl& handle )
629 if( handle.markerActor )
631 handle.markerActor.SetSize( 0, handle.lineHeight );
635 void CreateGrabHandle()
637 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
638 if( !grabHandle.actor )
640 grabHandle.actor = ImageActor::New( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] );
641 grabHandle.actor.SetSortModifier( DECORATION_DEPTH_INDEX );
642 grabHandle.actor.SetAnchorPoint( AnchorPoint::TOP_CENTER );
643 // Area that Grab handle responds to, larger than actual handle so easier to move
644 #ifdef DECORATOR_DEBUG
645 grabHandle.actor.SetName( "GrabHandleActor" );
646 if ( Dali::Internal::gLogFilter->IsEnabledFor( Debug::Verbose ) )
648 grabHandle.grabArea = Toolkit::CreateSolidColorActor( Vector4(0.0f, 0.0f, 0.0f, 0.0f), true, Color::RED, 1 );
649 grabHandle.grabArea.SetName( "GrabArea" );
653 grabHandle.grabArea = Actor::New();
654 grabHandle.grabArea.SetName( "GrabArea" );
657 grabHandle.grabArea = Actor::New();
660 grabHandle.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
661 grabHandle.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
662 grabHandle.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
663 grabHandle.grabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
664 grabHandle.actor.Add( grabHandle.grabArea );
665 grabHandle.actor.SetColor( mHandleColor );
667 grabHandle.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnGrabHandleTouched );
668 mTapDetector.Attach( grabHandle.grabArea );
669 mPanGestureDetector.Attach( grabHandle.grabArea );
671 mActiveLayer.Add( grabHandle.actor );
674 if( !grabHandle.actor.GetParent() )
676 mActiveLayer.Add( grabHandle.actor );
680 void CreateHandleMarker( HandleImpl& handle, Image& image, HandleType handleType )
684 handle.markerActor = ImageActor::New( image );
685 handle.markerActor.SetColor( mHandleColor );
686 handle.actor.Add( handle.markerActor );
688 handle.markerActor.SetResizePolicy ( ResizePolicy::FIXED, Dimension::HEIGHT );
690 if( LEFT_SELECTION_HANDLE == handleType )
692 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_RIGHT );
693 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_RIGHT );
695 else if( RIGHT_SELECTION_HANDLE == handleType )
697 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_LEFT );
698 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
703 void CreateSelectionHandles()
705 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
708 primary.actor = ImageActor::New( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
709 #ifdef DECORATOR_DEBUG
710 primary.actor.SetName("SelectionHandleOne");
712 primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
713 primary.actor.SetSortModifier( DECORATION_DEPTH_INDEX );
714 primary.actor.SetColor( mHandleColor );
716 primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
717 #ifdef DECORATOR_DEBUG
718 primary.grabArea.SetName("SelectionHandleOneGrabArea");
720 primary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
721 primary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
722 primary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
723 primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
725 mTapDetector.Attach( primary.grabArea );
726 mPanGestureDetector.Attach( primary.grabArea );
727 primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched );
729 primary.actor.Add( primary.grabArea );
731 CreateHandleMarker( primary, mHandleImages[LEFT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], LEFT_SELECTION_HANDLE );
734 if( !primary.actor.GetParent() )
736 mActiveLayer.Add( primary.actor );
739 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
740 if( !secondary.actor )
742 secondary.actor = ImageActor::New( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
743 #ifdef DECORATOR_DEBUG
744 secondary.actor.SetName("SelectionHandleTwo");
746 secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text.
747 secondary.actor.SetSortModifier( DECORATION_DEPTH_INDEX );
748 secondary.actor.SetColor( mHandleColor );
750 secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
751 #ifdef DECORATOR_DEBUG
752 secondary.grabArea.SetName("SelectionHandleTwoGrabArea");
754 secondary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
755 secondary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
756 secondary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
757 secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
759 mTapDetector.Attach( secondary.grabArea );
760 mPanGestureDetector.Attach( secondary.grabArea );
761 secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched );
763 secondary.actor.Add( secondary.grabArea );
765 CreateHandleMarker( secondary, mHandleImages[RIGHT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], RIGHT_SELECTION_HANDLE );
768 if( !secondary.actor.GetParent() )
770 mActiveLayer.Add( secondary.actor );
774 void CalculateHandleWorldCoordinates( HandleImpl& handle, Vector2& position )
776 // Get the world position of the active layer
777 const Vector3 parentWorldPosition = mActiveLayer.GetCurrentWorldPosition();
779 // Get the size of the UI control.
781 mController.GetTargetSize( targetSize );
783 // The grab handle position in world coords.
784 position.x = parentWorldPosition.x - 0.5f * targetSize.width + handle.position.x;
785 position.y = parentWorldPosition.y - 0.5f * targetSize.height + handle.position.y + handle.lineHeight;
788 void SetGrabHandlePosition()
790 // Reference to the grab handle.
791 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
793 // The grab handle position in world coords.
794 Vector2 grabHandleWorldPosition;
795 CalculateHandleWorldCoordinates( grabHandle, grabHandleWorldPosition );
797 // Check if the grab handle exceeds the boundaries of the decoration box.
798 // At the moment only the height is checked for the grab handle.
800 grabHandle.verticallyFlipped = ( grabHandle.verticallyFlippedPreferred &&
801 ( ( grabHandleWorldPosition.y - grabHandle.lineHeight - grabHandle.size.height ) > mBoundingBox.y ) ) ||
802 ( grabHandleWorldPosition.y + grabHandle.size.height > mBoundingBox.w );
804 // The grab handle 'y' position in local coords.
805 // If the grab handle exceeds the bottom of the decoration box,
806 // set the 'y' position to the top of the line.
807 // The SetGrabHandleImage() method will change the orientation.
808 const float yLocalPosition = grabHandle.verticallyFlipped ? grabHandle.position.y : grabHandle.position.y + grabHandle.lineHeight;
810 grabHandle.actor.SetPosition( grabHandle.position.x + floor( 0.5f * mCursorWidth ),
811 yLocalPosition ); // TODO : Fix for multiline.
814 void SetSelectionHandlePosition( HandleType type )
816 const bool isPrimaryHandle = LEFT_SELECTION_HANDLE == type;
818 // Reference to the selection handle.
819 HandleImpl& handle = mHandle[type];
821 // Get the world coordinates of the handle position.
822 Vector2 handleWorldPosition;
823 CalculateHandleWorldCoordinates( handle, handleWorldPosition );
825 // Whether to flip the handle (horizontally).
826 bool flipHandle = isPrimaryHandle ? mFlipLeftSelectionHandleDirection : mFlipRightSelectionHandleDirection;
828 // Whether to flip the handles if they are crossed.
829 bool crossFlip = false;
830 if( mFlipSelectionHandlesOnCross || !mHandlePanning )
832 crossFlip = mHandleCurrentCrossed;
835 // Does not flip if both conditions are true (double flip)
836 flipHandle = flipHandle != ( crossFlip || mHandlePreviousCrossed );
838 // Will flip the handles vertically if the user prefers it.
839 bool verticallyFlippedPreferred = handle.verticallyFlippedPreferred;
841 if( crossFlip || mHandlePreviousCrossed )
843 if( isPrimaryHandle )
845 verticallyFlippedPreferred = mHandle[RIGHT_SELECTION_HANDLE].verticallyFlippedPreferred;
849 verticallyFlippedPreferred = mHandle[LEFT_SELECTION_HANDLE].verticallyFlippedPreferred;
853 // Check if the selection handle exceeds the boundaries of the decoration box.
854 const bool exceedsLeftEdge = ( isPrimaryHandle ? !flipHandle : flipHandle ) && ( handleWorldPosition.x - handle.size.width < mBoundingBox.x );
856 const bool exceedsRightEdge = ( isPrimaryHandle ? flipHandle : !flipHandle ) && ( handleWorldPosition.x + handle.size.width > mBoundingBox.z );
858 // Does not flip if both conditions are true (double flip)
859 flipHandle = flipHandle != ( exceedsLeftEdge || exceedsRightEdge );
863 if( !handle.horizontallyFlipped )
865 // Change the anchor point to flip the image.
866 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_LEFT : AnchorPoint::TOP_RIGHT );
868 handle.horizontallyFlipped = true;
873 if( handle.horizontallyFlipped )
875 // Reset the anchor point.
876 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_RIGHT : AnchorPoint::TOP_LEFT );
878 handle.horizontallyFlipped = false;
882 // Whether to flip the handle vertically.
883 handle.verticallyFlipped = ( verticallyFlippedPreferred &&
884 ( ( handleWorldPosition.y - handle.lineHeight - handle.size.height ) > mBoundingBox.y ) ) ||
885 ( handleWorldPosition.y + handle.size.height > mBoundingBox.w );
887 // The primary selection handle 'y' position in local coords.
888 // If the handle exceeds the bottom of the decoration box,
889 // set the 'y' position to the top of the line.
890 // The SetHandleImage() method will change the orientation.
891 const float yLocalPosition = handle.verticallyFlipped ? handle.position.y : handle.position.y + handle.lineHeight;
893 handle.actor.SetPosition( handle.position.x,
894 yLocalPosition ); // TODO : Fix for multiline.
897 void SetHandleImage( HandleType type )
899 HandleImpl& handle = mHandle[type];
901 HandleType markerType = HANDLE_TYPE_COUNT;
902 // If the selection handle is flipped it chooses the image of the other selection handle. Does nothing for the grab handle.
903 if( LEFT_SELECTION_HANDLE == type )
905 type = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE;
906 markerType = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE_MARKER : LEFT_SELECTION_HANDLE_MARKER;
908 else if( RIGHT_SELECTION_HANDLE == type )
910 type = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE : RIGHT_SELECTION_HANDLE;
911 markerType = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE_MARKER : RIGHT_SELECTION_HANDLE_MARKER;
914 // Chooses between the released or pressed image. It checks whether the pressed image exists.
917 const HandleImageType imageType = ( handle.pressed ? ( mHandleImages[type][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
919 handle.actor.SetImage( mHandleImages[type][imageType] );
922 if( HANDLE_TYPE_COUNT != markerType )
924 if( handle.markerActor )
926 const HandleImageType markerImageType = ( handle.pressed ? ( mHandleImages[markerType][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
927 handle.markerActor.SetImage( mHandleImages[markerType][markerImageType] );
931 // Whether to flip the handle vertically.
934 handle.actor.SetOrientation( handle.verticallyFlipped ? ANGLE_180 : ANGLE_0, Vector3::XAXIS );
938 void CreateHighlight()
940 if( !mHighlightActor )
942 mHighlightActor = Actor::New();
944 #ifdef DECORATOR_DEBUG
945 mHighlightActor.SetName( "HighlightActor" );
947 mHighlightActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
948 mHighlightActor.SetSize( 1.0f, 1.0f );
949 mHighlightActor.SetColor( mHighlightColor );
950 mHighlightActor.SetColorMode( USE_OWN_COLOR );
953 // Add the highlight box telling the controller it needs clipping.
954 mController.AddDecoration( mHighlightActor, true );
957 void UpdateHighlight()
959 if ( mHighlightActor )
961 if( !mHighlightQuadList.empty() )
963 Vector< Vector2 > vertices;
964 Vector< unsigned int> indices;
967 std::vector<QuadCoordinates>::iterator iter = mHighlightQuadList.begin();
968 std::vector<QuadCoordinates>::iterator endIter = mHighlightQuadList.end();
970 for( std::size_t v = 0; iter != endIter; ++iter,v+=4 )
972 QuadCoordinates& quad = *iter;
975 vertex.x = quad.min.x;
976 vertex.y = quad.min.y;
977 vertices.PushBack( vertex );
980 vertex.x = quad.max.x;
981 vertex.y = quad.min.y;
982 vertices.PushBack( vertex );
985 vertex.x = quad.min.x;
986 vertex.y = quad.max.y;
987 vertices.PushBack( vertex );
989 // bottom-right (v+3)
990 vertex.x = quad.max.x;
991 vertex.y = quad.max.y;
992 vertices.PushBack( vertex );
994 // triangle A (3, 1, 0)
995 indices.PushBack( v + 3 );
996 indices.PushBack( v + 1 );
997 indices.PushBack( v );
999 // triangle B (0, 2, 3)
1000 indices.PushBack( v );
1001 indices.PushBack( v + 2 );
1002 indices.PushBack( v + 3 );
1007 mQuadVertices.SetSize( vertices.Size() );
1011 mQuadVertices = PropertyBuffer::New( mQuadVertexFormat, vertices.Size() );
1016 mQuadIndices.SetSize( indices.Size() );
1020 mQuadIndices = PropertyBuffer::New( mQuadIndexFormat, indices.Size() );
1023 mQuadVertices.SetData( &vertices[ 0 ] );
1024 mQuadIndices.SetData( &indices[ 0 ] );
1026 if( !mQuadGeometry )
1028 mQuadGeometry = Geometry::New();
1029 mQuadGeometry.AddVertexBuffer( mQuadVertices );
1031 mQuadGeometry.SetIndexBuffer( mQuadIndices );
1033 if( !mHighlightRenderer )
1035 mHighlightRenderer = Dali::Renderer::New( mQuadGeometry, mHighlightMaterial );
1036 mHighlightActor.AddRenderer( mHighlightRenderer );
1040 mHighlightActor.SetPosition( mHighlightPosition.x,
1041 mHighlightPosition.y );
1043 mHighlightQuadList.clear();
1045 if( mHighlightRenderer )
1047 mHighlightRenderer.SetDepthIndex( mTextDepth - 2u ); // text is rendered at mTextDepth and text's shadow at mTextDepth -1u.
1052 void OnTap( Actor actor, const TapGesture& tap )
1054 if( actor == mHandle[GRAB_HANDLE].actor )
1060 void DoPan( HandleImpl& handle, HandleType type, const PanGesture& gesture )
1062 if( Gesture::Started == gesture.state )
1064 handle.grabDisplacementX = handle.grabDisplacementY = 0;
1067 handle.grabDisplacementX += gesture.displacement.x;
1068 handle.grabDisplacementY += gesture.displacement.y;
1070 const float x = handle.position.x + handle.grabDisplacementX;
1071 const float y = handle.position.y + handle.lineHeight*0.5f + handle.grabDisplacementY;
1073 if( Gesture::Started == gesture.state ||
1074 Gesture::Continuing == gesture.state )
1077 mController.GetTargetSize( targetSize );
1079 if( x < mScrollThreshold )
1081 mScrollDirection = SCROLL_RIGHT;
1082 mHandleScrolling = type;
1085 else if( x > targetSize.width - mScrollThreshold )
1087 mScrollDirection = SCROLL_LEFT;
1088 mHandleScrolling = type;
1093 mHandleScrolling = HANDLE_TYPE_COUNT;
1095 mController.DecorationEvent( type, HANDLE_PRESSED, x, y );
1098 mHandlePanning = true;
1100 else if( Gesture::Finished == gesture.state ||
1101 Gesture::Cancelled == gesture.state )
1104 ( mScrollTimer.IsRunning() || mNotifyEndOfScroll ) )
1106 mNotifyEndOfScroll = false;
1107 mHandleScrolling = HANDLE_TYPE_COUNT;
1109 mController.DecorationEvent( type, HANDLE_STOP_SCROLLING, x, y );
1113 mController.DecorationEvent( type, HANDLE_RELEASED, x, y );
1116 handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_RELEASED] );
1117 handle.pressed = false;
1119 mHandlePanning = false;
1123 void OnPan( Actor actor, const PanGesture& gesture )
1125 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1126 HandleImpl& primarySelectionHandle = mHandle[LEFT_SELECTION_HANDLE];
1127 HandleImpl& secondarySelectionHandle = mHandle[RIGHT_SELECTION_HANDLE];
1129 if( actor == grabHandle.grabArea )
1131 DoPan( grabHandle, GRAB_HANDLE, gesture );
1133 else if( actor == primarySelectionHandle.grabArea )
1135 DoPan( primarySelectionHandle, LEFT_SELECTION_HANDLE, gesture );
1137 else if( actor == secondarySelectionHandle.grabArea )
1139 DoPan( secondarySelectionHandle, RIGHT_SELECTION_HANDLE, gesture );
1143 bool OnGrabHandleTouched( Actor actor, const TouchEvent& event )
1145 // Switch between pressed/release grab-handle images
1146 if( event.GetPointCount() > 0 &&
1147 mHandle[GRAB_HANDLE].actor )
1149 const TouchPoint& point = event.GetPoint(0);
1151 if( TouchPoint::Down == point.state )
1153 mHandle[GRAB_HANDLE].pressed = true;
1155 else if( ( TouchPoint::Up == point.state ) ||
1156 ( TouchPoint::Interrupted == point.state ) )
1158 mHandle[GRAB_HANDLE].pressed = false;
1161 SetHandleImage( GRAB_HANDLE );
1164 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1168 bool OnHandleOneTouched( Actor actor, const TouchEvent& event )
1170 // Switch between pressed/release selection handle images
1171 if( event.GetPointCount() > 0 &&
1172 mHandle[LEFT_SELECTION_HANDLE].actor )
1174 const TouchPoint& point = event.GetPoint(0);
1176 if( TouchPoint::Down == point.state )
1178 mHandle[LEFT_SELECTION_HANDLE].pressed = true;
1180 else if( ( TouchPoint::Up == point.state ) ||
1181 ( TouchPoint::Interrupted == point.state ) )
1183 mHandle[LEFT_SELECTION_HANDLE].pressed = false;
1184 mHandlePreviousCrossed = mHandleCurrentCrossed;
1185 mHandlePanning = false;
1188 SetHandleImage( LEFT_SELECTION_HANDLE );
1191 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1195 bool OnHandleTwoTouched( Actor actor, const TouchEvent& event )
1197 // Switch between pressed/release selection handle images
1198 if( event.GetPointCount() > 0 &&
1199 mHandle[RIGHT_SELECTION_HANDLE].actor )
1201 const TouchPoint& point = event.GetPoint(0);
1203 if( TouchPoint::Down == point.state )
1205 mHandle[RIGHT_SELECTION_HANDLE].pressed = true;
1207 else if( ( TouchPoint::Up == point.state ) ||
1208 ( TouchPoint::Interrupted == point.state ) )
1210 mHandle[RIGHT_SELECTION_HANDLE].pressed = false;
1211 mHandlePreviousCrossed = mHandleCurrentCrossed;
1212 mHandlePanning = false;
1215 SetHandleImage( RIGHT_SELECTION_HANDLE );
1218 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1224 float AlternatePopUpPositionRelativeToCursor()
1226 float alternativePosition = 0.0f;
1228 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1230 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1231 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1232 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1233 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
1235 if( primaryHandle.active || secondaryHandle.active )
1237 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
1238 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
1242 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + grabHandle.size.height + cursor.position.y;
1245 return alternativePosition;
1248 void PopUpLeavesVerticalBoundary( PropertyNotification& source )
1250 float alternativeYPosition = 0.0f;
1251 // todo use AlternatePopUpPositionRelativeToSelectionHandles() if text is highlighted
1252 // if can't be positioned above, then position below row.
1253 alternativeYPosition = AlternatePopUpPositionRelativeToCursor();
1255 mCopyPastePopup.actor.SetY( alternativeYPosition );
1258 void SetUpPopupPositionNotifications( )
1260 // Note Property notifications ignore any set anchor point so conditions must allow for this. Default is Top Left.
1262 // Exceeding vertical boundary
1264 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1266 PropertyNotification verticalExceedNotification = mCopyPastePopup.actor.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1267 OutsideCondition( mBoundingBox.y + popupHeight * 0.5f,
1268 mBoundingBox.w - popupHeight * 0.5f ) );
1270 verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary );
1273 void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, const Vector3& popupDistanceFromAnchorPoint, Actor parent, const Vector4& boundingRectangleWorld )
1275 DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() )
1277 // Parent must already by added to Stage for these Get calls to work
1278 const Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize() * parent.GetCurrentAnchorPoint();
1279 const Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition; // Parent World position plus popup local position gives World Position
1281 // Calculate distance to move popup (in local space) so fits within the boundary
1282 float xOffSetToKeepWithinBounds = 0.0f;
1283 if( popupWorldPosition.x - popupDistanceFromAnchorPoint.x < boundingRectangleWorld.x )
1285 xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x );
1287 else if( popupWorldPosition.x + popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z )
1289 xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x + popupDistanceFromAnchorPoint.x );
1292 // Ensure initial display of Popup is in alternative position if can not fit above. As Property notification will be a frame behind.
1293 if( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y )
1295 requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor();
1298 requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds;
1300 // Prevent pixel mis-alignment by rounding down.
1301 requiredPopupPosition.x = floor( requiredPopupPosition.x );
1302 requiredPopupPosition.y = floor( requiredPopupPosition.y );
1305 void SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1307 HandleImpl& handle = mHandle[handleType];
1308 handle.size = Size( image.GetWidth(), image.GetHeight() );
1310 mHandleImages[handleType][handleImageType] = image;
1313 void SetScrollThreshold( float threshold )
1315 mScrollThreshold = threshold;
1318 float GetScrollThreshold() const
1320 return mScrollThreshold;
1323 void SetScrollSpeed( float speed )
1325 mScrollSpeed = speed;
1326 mScrollDistance = speed * SCROLL_TICK_INTERVAL * TO_SECONDS;
1329 float GetScrollSpeed() const
1331 return mScrollSpeed;
1334 void NotifyEndOfScroll()
1340 mNotifyEndOfScroll = true;
1345 * Creates and starts a timer to scroll the text when handles are close to the edges of the text.
1347 * It only starts the timer if it's already created.
1349 void StartScrollTimer()
1353 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1354 mScrollTimer.TickSignal().Connect( this, &Decorator::Impl::OnScrollTimerTick );
1357 if( !mScrollTimer.IsRunning() )
1359 mScrollTimer.Start();
1364 * Stops the timer used to scroll the text.
1366 void StopScrollTimer()
1370 mScrollTimer.Stop();
1375 * Callback called by the timer used to scroll the text.
1377 * It calculates and sets a new scroll position.
1379 bool OnScrollTimerTick()
1381 if( HANDLE_TYPE_COUNT != mHandleScrolling )
1383 mController.DecorationEvent( mHandleScrolling,
1385 mScrollDirection == SCROLL_RIGHT ? mScrollDistance : -mScrollDistance,
1392 ControllerInterface& mController;
1394 TapGestureDetector mTapDetector;
1395 PanGestureDetector mPanGestureDetector;
1396 Timer mCursorBlinkTimer; ///< Timer to signal cursor to blink
1397 Timer mScrollTimer; ///< Timer used to scroll the text when the grab handle is moved close to the edges.
1399 Layer mActiveLayer; ///< Layer for active handles and alike that ensures they are above all else.
1400 ImageActor mPrimaryCursor;
1401 ImageActor mSecondaryCursor;
1403 Actor mHighlightActor; ///< Actor to display highlight
1404 Renderer mHighlightRenderer;
1405 Material mHighlightMaterial; ///< Material used for highlight
1406 Property::Map mQuadVertexFormat;
1407 Property::Map mQuadIndexFormat;
1408 PopupImpl mCopyPastePopup;
1409 TextSelectionPopup::Buttons mEnabledPopupButtons; /// Bit mask of currently enabled Popup buttons
1410 TextSelectionPopupCallbackInterface& mTextSelectionPopupCallbackInterface;
1412 Image mHandleImages[HANDLE_TYPE_COUNT][HANDLE_IMAGE_TYPE_COUNT];
1413 Vector4 mHandleColor;
1415 CursorImpl mCursor[CURSOR_COUNT];
1416 HandleImpl mHandle[HANDLE_TYPE_COUNT];
1418 PropertyBuffer mQuadVertices;
1419 PropertyBuffer mQuadIndices;
1420 Geometry mQuadGeometry;
1421 QuadContainer mHighlightQuadList; ///< Sub-selections that combine to create the complete selection highlight
1423 Vector4 mBoundingBox; ///< The bounding box in world coords.
1424 Vector4 mHighlightColor; ///< Color of the highlight
1425 Vector2 mHighlightPosition; ///< The position of the highlight actor.
1427 unsigned int mActiveCursor;
1428 unsigned int mCursorBlinkInterval;
1429 float mCursorBlinkDuration;
1430 float mCursorWidth; ///< The width of the cursors in pixels.
1431 HandleType mHandleScrolling; ///< The handle which is scrolling.
1432 ScrollDirection mScrollDirection; ///< The direction of the scroll.
1433 float mScrollThreshold; ///< Defines a square area inside the control, close to the edge. A cursor entering this area will trigger scroll events.
1434 float mScrollSpeed; ///< The scroll speed in pixels per second.
1435 float mScrollDistance; ///< Distance the text scrolls during a scroll interval.
1436 int mTextDepth; ///< The depth used to render the text.
1438 bool mActiveCopyPastePopup : 1;
1439 bool mPopupSetNewPosition : 1;
1440 bool mCursorBlinkStatus : 1; ///< Flag to switch between blink on and blink off.
1441 bool mDelayCursorBlink : 1; ///< Used to avoid cursor blinking when entering text.
1442 bool mPrimaryCursorVisible : 1; ///< Whether the primary cursor is visible.
1443 bool mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible.
1444 bool mFlipSelectionHandlesOnCross : 1; ///< Whether to flip the selection handles as soon as they cross.
1445 bool mFlipLeftSelectionHandleDirection : 1; ///< Whether to flip the left selection handle image because of the character's direction.
1446 bool mFlipRightSelectionHandleDirection : 1; ///< Whether to flip the right selection handle image because of the character's direction.
1447 bool mHandlePanning : 1; ///< Whether any of the handles is moving.
1448 bool mHandleCurrentCrossed : 1; ///< Whether the handles are crossed.
1449 bool mHandlePreviousCrossed : 1; ///< Whether the handles where crossed at the last handle touch up.
1450 bool mNotifyEndOfScroll : 1; ///< Whether to notify the end of the scroll.
1453 DecoratorPtr Decorator::New( ControllerInterface& controller,
1454 TextSelectionPopupCallbackInterface& callbackInterface )
1456 return DecoratorPtr( new Decorator( controller,
1457 callbackInterface ) );
1460 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
1462 LocalToWorldCoordinatesBoundingBox( boundingBox, mImpl->mBoundingBox );
1465 void Decorator::GetBoundingBox( Rect<int>& boundingBox ) const
1467 WorldToLocalCoordinatesBoundingBox( mImpl->mBoundingBox, boundingBox );
1470 void Decorator::Relayout( const Vector2& size )
1472 mImpl->Relayout( size );
1475 void Decorator::UpdatePositions( const Vector2& scrollOffset )
1477 mImpl->UpdatePositions( scrollOffset );
1482 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
1484 mImpl->mActiveCursor = activeCursor;
1487 unsigned int Decorator::GetActiveCursor() const
1489 return mImpl->mActiveCursor;
1492 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
1494 mImpl->mCursor[cursor].position.x = x;
1495 mImpl->mCursor[cursor].position.y = y;
1496 mImpl->mCursor[cursor].cursorHeight = cursorHeight;
1497 mImpl->mCursor[cursor].lineHeight = lineHeight;
1500 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
1502 x = mImpl->mCursor[cursor].position.x;
1503 y = mImpl->mCursor[cursor].position.y;
1504 cursorHeight = mImpl->mCursor[cursor].cursorHeight;
1505 lineHeight = mImpl->mCursor[cursor].lineHeight;
1508 const Vector2& Decorator::GetPosition( Cursor cursor ) const
1510 return mImpl->mCursor[cursor].position;
1513 void Decorator::SetCursorColor( Cursor cursor, const Dali::Vector4& color )
1515 mImpl->mCursor[cursor].color = color;
1518 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
1520 return mImpl->mCursor[cursor].color;
1523 void Decorator::StartCursorBlink()
1525 if ( !mImpl->mCursorBlinkTimer )
1527 mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
1528 mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
1531 if ( !mImpl->mCursorBlinkTimer.IsRunning() )
1533 mImpl->mCursorBlinkTimer.Start();
1537 void Decorator::StopCursorBlink()
1539 if ( mImpl->mCursorBlinkTimer )
1541 mImpl->mCursorBlinkTimer.Stop();
1544 mImpl->mCursorBlinkStatus = true; // Keep cursor permanently shown
1547 void Decorator::DelayCursorBlink()
1549 mImpl->mCursorBlinkStatus = true; // Show cursor for a bit longer
1550 mImpl->mDelayCursorBlink = true;
1553 void Decorator::SetCursorBlinkInterval( float seconds )
1555 mImpl->mCursorBlinkInterval = static_cast<unsigned int>( seconds * TO_MILLISECONDS ); // Convert to milliseconds
1558 float Decorator::GetCursorBlinkInterval() const
1560 return static_cast<float>( mImpl->mCursorBlinkInterval ) * TO_SECONDS;
1563 void Decorator::SetCursorBlinkDuration( float seconds )
1565 mImpl->mCursorBlinkDuration = seconds;
1568 float Decorator::GetCursorBlinkDuration() const
1570 return mImpl->mCursorBlinkDuration;
1573 void Decorator::SetCursorWidth( int width )
1575 mImpl->mCursorWidth = static_cast<float>( width );
1578 int Decorator::GetCursorWidth() const
1580 return static_cast<int>( mImpl->mCursorWidth );
1585 void Decorator::SetHandleActive( HandleType handleType, bool active )
1587 mImpl->mHandle[handleType].active = active;
1591 if( ( LEFT_SELECTION_HANDLE == handleType ) || ( RIGHT_SELECTION_HANDLE == handleType ) )
1593 mImpl->mHandlePreviousCrossed = false;
1596 // TODO: this is a work-around.
1597 // The problem is the handle actor does not receive the touch event with the Interrupt
1598 // state when the power button is pressed and the application goes to background.
1599 mImpl->mHandle[handleType].pressed = false;
1600 Image imageReleased = mImpl->mHandleImages[handleType][HANDLE_IMAGE_RELEASED];
1601 ImageActor imageActor = mImpl->mHandle[handleType].actor;
1602 if( imageReleased && imageActor )
1604 imageActor.SetImage( imageReleased );
1610 bool Decorator::IsHandleActive( HandleType handleType ) const
1612 return mImpl->mHandle[handleType].active ;
1615 void Decorator::SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1617 mImpl->SetHandleImage( handleType, handleImageType, image );
1620 Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType handleImageType ) const
1622 return mImpl->mHandleImages[handleType][handleImageType];
1625 void Decorator::SetHandleColor( const Vector4& color )
1627 mImpl->mHandleColor = color;
1630 const Vector4& Decorator::GetHandleColor() const
1632 return mImpl->mHandleColor;
1635 void Decorator::SetPosition( HandleType handleType, float x, float y, float height )
1637 // Adjust grab handle displacement
1638 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1640 handle.grabDisplacementX -= x - handle.position.x;
1641 handle.grabDisplacementY -= y - handle.position.y;
1643 handle.position.x = x;
1644 handle.position.y = y;
1645 handle.lineHeight = height;
1648 void Decorator::GetPosition( HandleType handleType, float& x, float& y, float& height ) const
1650 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1652 x = handle.position.x;
1653 y = handle.position.y;
1654 height = handle.lineHeight;
1657 const Vector2& Decorator::GetPosition( HandleType handleType ) const
1659 return mImpl->mHandle[handleType].position;
1662 void Decorator::FlipHandleVertically( HandleType handleType, bool flip )
1664 mImpl->mHandle[handleType].verticallyFlippedPreferred = flip;
1667 bool Decorator::IsHandleVerticallyFlipped( HandleType handleType ) const
1669 return mImpl->mHandle[handleType].verticallyFlippedPreferred;
1672 void Decorator::FlipSelectionHandlesOnCrossEnabled( bool enable )
1674 mImpl->mFlipSelectionHandlesOnCross = enable;
1677 void Decorator::SetSelectionHandleFlipState( bool indicesSwapped, bool left, bool right )
1679 mImpl->mHandleCurrentCrossed = indicesSwapped;
1680 mImpl->mFlipLeftSelectionHandleDirection = left;
1681 mImpl->mFlipRightSelectionHandleDirection = right;
1684 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1686 mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1689 void Decorator::ClearHighlights()
1691 mImpl->mHighlightQuadList.clear();
1692 mImpl->mHighlightPosition = Vector2::ZERO;
1695 void Decorator::SetHighlightColor( const Vector4& color )
1697 mImpl->mHighlightColor = color;
1700 const Vector4& Decorator::GetHighlightColor() const
1702 return mImpl->mHighlightColor;
1705 void Decorator::SetTextDepth( int textDepth )
1707 mImpl->mTextDepth = textDepth;
1710 void Decorator::SetPopupActive( bool active )
1712 mImpl->mActiveCopyPastePopup = active;
1715 bool Decorator::IsPopupActive() const
1717 return mImpl->mActiveCopyPastePopup ;
1720 void Decorator::SetEnabledPopupButtons( TextSelectionPopup::Buttons& enabledButtonsBitMask )
1722 mImpl->mEnabledPopupButtons = enabledButtonsBitMask;
1724 if ( !mImpl->mCopyPastePopup.actor )
1726 mImpl->mCopyPastePopup.actor = TextSelectionPopup::New( &mImpl->mTextSelectionPopupCallbackInterface );
1727 #ifdef DECORATOR_DEBUG
1728 mImpl->mCopyPastePopup.actor.SetName("mCopyPastePopup");
1730 mImpl->mCopyPastePopup.actor.SetAnchorPoint( AnchorPoint::CENTER );
1731 mImpl->mCopyPastePopup.actor.OnRelayoutSignal().Connect( mImpl, &Decorator::Impl::PopupRelayoutComplete ); // Position popup after size negotiation
1734 mImpl->mCopyPastePopup.actor.EnableButtons( mImpl->mEnabledPopupButtons );
1737 TextSelectionPopup::Buttons& Decorator::GetEnabledPopupButtons()
1739 return mImpl->mEnabledPopupButtons;
1744 void Decorator::SetScrollThreshold( float threshold )
1746 mImpl->SetScrollThreshold( threshold );
1749 float Decorator::GetScrollThreshold() const
1751 return mImpl->GetScrollThreshold();
1754 void Decorator::SetScrollSpeed( float speed )
1756 mImpl->SetScrollSpeed( speed );
1759 float Decorator::GetScrollSpeed() const
1761 return mImpl->GetScrollSpeed();
1764 void Decorator::NotifyEndOfScroll()
1766 mImpl->NotifyEndOfScroll();
1769 Decorator::~Decorator()
1774 Decorator::Decorator( ControllerInterface& controller,
1775 TextSelectionPopupCallbackInterface& callbackInterface )
1778 mImpl = new Decorator::Impl( controller, callbackInterface );
1783 } // namespace Toolkit