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/image-view/image-view.h>
37 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
38 #include <dali-toolkit/internal/controls/image-view/image-view-impl.h>
41 #define DECORATOR_DEBUG
45 #define MAKE_SHADER(A)#A
49 const char* VERTEX_SHADER = MAKE_SHADER(
50 attribute mediump vec2 aPosition;
51 uniform mediump mat4 uMvpMatrix;
52 uniform mediump vec3 uSize;
56 mediump vec4 position = vec4( aPosition, 0.0, 1.0 );
57 position.xyz *= uSize;
58 gl_Position = uMvpMatrix * position;
62 const char* FRAGMENT_SHADER = MAKE_SHADER(
63 uniform lowp vec4 uColor;
67 gl_FragColor = uColor;
78 #ifdef DECORATOR_DEBUG
79 Integration::Log::Filter* gLogFilter( Integration::Log::Filter::New(Debug::NoLogging, false, "LOG_TEXT_DECORATOR") );
89 const Dali::Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.25f, 1.5f, 1.0f );
90 const Dali::Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.25f, 1.5f, 1.0f );
92 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.
94 const Dali::Vector4 HANDLE_COLOR( 0.0f, (183.0f / 255.0f), (229.0f / 255.0f), 1.0f );
96 const unsigned int CURSOR_BLINK_INTERVAL = 500u; // Cursor blink interval
97 const float TO_MILLISECONDS = 1000.f;
98 const float TO_SECONDS = 1.f / TO_MILLISECONDS;
100 const unsigned int SCROLL_TICK_INTERVAL = 50u;
102 const float SCROLL_THRESHOLD = 10.f;
103 const float SCROLL_SPEED = 300.f;
104 const float SCROLL_DISTANCE = SCROLL_SPEED * SCROLL_TICK_INTERVAL * TO_SECONDS;
106 const float CURSOR_WIDTH = 1.f;
109 * structure to hold coordinates of each quad, which will make up the mesh.
111 struct QuadCoordinates
114 * Default constructor
122 * @param[in] x1 left co-ordinate
123 * @param[in] y1 top co-ordinate
124 * @param[in] x2 right co-ordinate
125 * @param[in] y2 bottom co-ordinate
127 QuadCoordinates(float x1, float y1, float x2, float y2)
133 Dali::Vector2 min; ///< top-left (minimum) position of quad
134 Dali::Vector2 max; ///< bottom-right (maximum) position of quad
137 typedef std::vector<QuadCoordinates> QuadContainer;
140 * @brief Takes a bounding rectangle in the local coordinates of an actor and returns the world coordinates Bounding Box.
141 * @param[in] boundingRectangle local bounding
142 * @param[out] Vector4 World coordinate bounding Box.
144 void LocalToWorldCoordinatesBoundingBox( const Dali::Rect<int>& boundingRectangle, Dali::Vector4& boundingBox )
146 // Convert to world coordinates and store as a Vector4 to be compatible with Property Notifications.
147 Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
149 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
150 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
152 boundingBox = Dali::Vector4( originX,
154 originX + boundingRectangle.width,
155 originY + boundingRectangle.height );
158 void WorldToLocalCoordinatesBoundingBox( const Dali::Vector4& boundingBox, Dali::Rect<int>& boundingRectangle )
160 // Convert to local coordinates and store as a Dali::Rect.
161 Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
163 boundingRectangle.x = boundingBox.x + 0.5f * stageSize.width;
164 boundingRectangle.y = boundingBox.y + 0.5f * stageSize.height;
165 boundingRectangle.width = boundingBox.z - boundingBox.x;
166 boundingRectangle.height = boundingBox.w - boundingBox.y;
169 } // end of namespace
180 struct Decorator::Impl : public ConnectionTracker
194 : color( Dali::Color::BLACK ),
196 cursorHeight( 0.0f ),
213 grabDisplacementX( 0.f ),
214 grabDisplacementY( 0.f ),
218 verticallyFlippedPreferred( false ),
219 horizontallyFlipped( false ),
220 verticallyFlipped( false )
226 ImageView markerActor;
230 float lineHeight; ///< Not the handle height
231 float grabDisplacementX;
232 float grabDisplacementY;
236 bool verticallyFlippedPreferred : 1; ///< Whether the handle is preferred to be vertically flipped.
237 bool horizontallyFlipped : 1; ///< Whether the handle has been horizontally flipped.
238 bool verticallyFlipped : 1; ///< Whether the handle has been vertically flipped.
248 TextSelectionPopup actor;
252 Impl( ControllerInterface& controller,
253 TextSelectionPopupCallbackInterface& callbackInterface )
254 : mController( controller ),
255 mEnabledPopupButtons( TextSelectionPopup::NONE ),
256 mTextSelectionPopupCallbackInterface( callbackInterface ),
257 mHandleColor( HANDLE_COLOR ),
259 mHighlightColor( LIGHT_BLUE ),
260 mHighlightPosition( Vector2::ZERO ),
261 mActiveCursor( ACTIVE_CURSOR_NONE ),
262 mCursorBlinkInterval( CURSOR_BLINK_INTERVAL ),
263 mCursorBlinkDuration( 0.0f ),
264 mCursorWidth( CURSOR_WIDTH ),
265 mHandleScrolling( HANDLE_TYPE_COUNT ),
266 mScrollDirection( SCROLL_NONE ),
267 mScrollThreshold( SCROLL_THRESHOLD ),
268 mScrollSpeed( SCROLL_SPEED ),
269 mScrollDistance( SCROLL_DISTANCE ),
271 mActiveCopyPastePopup( false ),
272 mPopupSetNewPosition( true ),
273 mCursorBlinkStatus( true ),
274 mDelayCursorBlink( false ),
275 mPrimaryCursorVisible( false ),
276 mSecondaryCursorVisible( false ),
277 mFlipSelectionHandlesOnCross( false ),
278 mFlipLeftSelectionHandleDirection( false ),
279 mFlipRightSelectionHandleDirection( false ),
280 mHandlePanning( false ),
281 mHandleCurrentCrossed( false ),
282 mHandlePreviousCrossed( false ),
283 mNotifyEndOfScroll( false )
285 mQuadVertexFormat[ "aPosition" ] = Property::VECTOR2;
286 mQuadIndexFormat[ "indices" ] = Property::INTEGER;
287 mHighlightMaterial = Material::New( Shader::New( VERTEX_SHADER, FRAGMENT_SHADER ) );
293 * Relayout of the decorations owned by the decorator.
294 * @param[in] size The Size of the UI control the decorator is adding it's decorations to.
296 void Relayout( const Vector2& size )
300 // TODO - Remove this if nothing is active
303 // Show or hide the cursors
308 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
309 mPrimaryCursorVisible = ( cursor.position.x + mCursorWidth <= mControlSize.width ) && ( cursor.position.x >= 0.f );
310 if( mPrimaryCursorVisible )
312 mPrimaryCursor.SetPosition( cursor.position.x,
314 mPrimaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) );
316 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
318 if( mSecondaryCursor )
320 const CursorImpl& cursor = mCursor[SECONDARY_CURSOR];
321 mSecondaryCursorVisible = ( cursor.position.x + mCursorWidth <= mControlSize.width ) && ( cursor.position.x >= 0.f );
322 if( mSecondaryCursorVisible )
324 mSecondaryCursor.SetPosition( cursor.position.x,
326 mSecondaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) );
328 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
331 // Show or hide the grab handle
332 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
333 bool newGrabHandlePosition = false;
334 if( grabHandle.active )
336 const bool isVisible = ( grabHandle.position.x + floor( 0.5f * mCursorWidth ) <= mControlSize.width ) && ( grabHandle.position.x >= 0.f );
342 // Sets the grab handle position and calculate if it needs to be vertically flipped if it exceeds the boundary box.
343 SetGrabHandlePosition();
345 // Sets the grab handle image according if it's pressed, flipped, etc.
346 SetHandleImage( GRAB_HANDLE );
348 newGrabHandlePosition = true;
351 if( grabHandle.actor )
353 grabHandle.actor.SetVisible( isVisible );
356 else if( grabHandle.actor )
358 grabHandle.actor.Unparent();
361 // Show or hide the selection handles/highlight
362 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
363 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
364 bool newPrimaryHandlePosition = false;
365 bool newSecondaryHandlePosition = false;
366 if( primary.active || secondary.active )
368 const bool isPrimaryVisible = ( primary.position.x <= mControlSize.width ) && ( primary.position.x >= 0.f );
369 const bool isSecondaryVisible = ( secondary.position.x <= mControlSize.width ) && ( secondary.position.x >= 0.f );
371 if( isPrimaryVisible || isSecondaryVisible )
373 CreateSelectionHandles();
375 if( isPrimaryVisible )
377 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
379 // Sets the primary handle image according if it's pressed, flipped, etc.
380 SetHandleImage( LEFT_SELECTION_HANDLE );
382 SetSelectionHandleMarkerSize( primary );
384 newPrimaryHandlePosition = true;
387 if( isSecondaryVisible )
389 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
391 // Sets the secondary handle image according if it's pressed, flipped, etc.
392 SetHandleImage( RIGHT_SELECTION_HANDLE );
394 SetSelectionHandleMarkerSize( secondary );
396 newSecondaryHandlePosition = true;
402 primary.actor.SetVisible( isPrimaryVisible );
404 if( secondary.actor )
406 secondary.actor.SetVisible( isSecondaryVisible );
416 primary.actor.Unparent();
418 if( secondary.actor )
420 secondary.actor.Unparent();
422 if( mHighlightActor )
424 mHighlightActor.Unparent();
428 if( newGrabHandlePosition ||
429 newPrimaryHandlePosition ||
430 newSecondaryHandlePosition )
432 // Setup property notifications to find whether the handles leave the boundaries of the current display.
433 SetupActiveLayerPropertyNotifications();
436 if( mActiveCopyPastePopup )
439 mPopupSetNewPosition = true;
443 if( mCopyPastePopup.actor )
445 mCopyPastePopup.actor.HidePopup();
446 mPopupSetNewPosition = true;
451 void UpdatePositions( const Vector2& scrollOffset )
453 mCursor[PRIMARY_CURSOR].position += scrollOffset;
454 mCursor[SECONDARY_CURSOR].position += scrollOffset;
455 mHandle[ GRAB_HANDLE ].position += scrollOffset;
456 mHandle[ LEFT_SELECTION_HANDLE ].position += scrollOffset;
457 mHandle[ RIGHT_SELECTION_HANDLE ].position += scrollOffset;
458 mHighlightPosition += scrollOffset;
463 if ( !mCopyPastePopup.actor )
468 if( !mCopyPastePopup.actor.GetParent() )
470 mActiveLayer.Add( mCopyPastePopup.actor );
473 mCopyPastePopup.actor.RaiseAbove( mActiveLayer );
474 mCopyPastePopup.actor.ShowPopup();
477 void DeterminePositionPopup()
479 if( !mActiveCopyPastePopup )
484 // Retrieves the popup's size after relayout.
485 const Vector3 popupSize = Vector3( mCopyPastePopup.actor.GetRelayoutSize( Dimension::WIDTH ), mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT ), 0.0f );
487 if( mPopupSetNewPosition )
489 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
490 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
491 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
492 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
494 if( primaryHandle.active || secondaryHandle.active )
496 // Calculates the popup's position if selection handles are active.
497 const float minHandleXPosition = std::min( primaryHandle.position.x, secondaryHandle.position.x );
498 const float maxHandleXPosition = std::max( primaryHandle.position.x, secondaryHandle.position.x );
499 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
501 mCopyPastePopup.position.x = minHandleXPosition + ( ( maxHandleXPosition - minHandleXPosition ) * 0.5f );
502 mCopyPastePopup.position.y = -0.5f * popupSize.height - maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
506 // Calculates the popup's position if the grab handle is active.
507 mCopyPastePopup.position = Vector3( cursor.position.x, -0.5f * popupSize.height - grabHandle.size.height + cursor.position.y, 0.0f );
511 // Checks if there is enough space above the text control. If not it places the popup under it.
512 GetConstrainedPopupPosition( mCopyPastePopup.position, popupSize * AnchorPoint::CENTER, mActiveLayer, mBoundingBox );
514 SetUpPopupPositionNotifications();
516 mCopyPastePopup.actor.SetPosition( mCopyPastePopup.position );
517 mPopupSetNewPosition = false;
520 void PopupRelayoutComplete( Actor actor )
522 // Size negotiation for CopyPastePopup complete so can get the size and constrain position within bounding box.
524 DeterminePositionPopup();
527 void CreateCursor( Control& cursor, const Vector4& color )
529 cursor = Control::New();
530 cursor.SetBackgroundColor( color );
531 cursor.SetParentOrigin( ParentOrigin::TOP_LEFT );
532 cursor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
535 // Add or Remove cursor(s) from parent
538 if( mActiveCursor == ACTIVE_CURSOR_NONE )
542 mPrimaryCursor.Unparent();
544 if( mSecondaryCursor )
546 mSecondaryCursor.Unparent();
551 // Create Primary and or Secondary Cursor(s) if active and add to parent
552 if ( mActiveCursor == ACTIVE_CURSOR_PRIMARY ||
553 mActiveCursor == ACTIVE_CURSOR_BOTH )
555 if ( !mPrimaryCursor )
557 CreateCursor( mPrimaryCursor, mCursor[PRIMARY_CURSOR].color );
558 #ifdef DECORATOR_DEBUG
559 mPrimaryCursor.SetName( "PrimaryCursorActor" );
563 if( !mPrimaryCursor.GetParent() )
565 mActiveLayer.Add( mPrimaryCursor );
569 if ( mActiveCursor == ACTIVE_CURSOR_BOTH )
571 if ( !mSecondaryCursor )
573 CreateCursor( mSecondaryCursor, mCursor[SECONDARY_CURSOR].color );
574 #ifdef DECORATOR_DEBUG
575 mSecondaryCursor.SetName( "SecondaryCursorActor" );
579 if( !mSecondaryCursor.GetParent() )
581 mActiveLayer.Add( mSecondaryCursor );
586 if( mSecondaryCursor )
588 mSecondaryCursor.Unparent();
594 bool OnCursorBlinkTimerTick()
596 if( !mDelayCursorBlink )
599 if ( mPrimaryCursor )
601 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
603 if ( mSecondaryCursor )
605 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
608 mCursorBlinkStatus = !mCursorBlinkStatus;
613 mDelayCursorBlink = false;
619 void SetupTouchEvents()
621 mTapDetector = TapGestureDetector::New();
622 mTapDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnTap );
624 mPanGestureDetector = PanGestureDetector::New();
625 mPanGestureDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan );
628 void CreateActiveLayer()
632 mActiveLayer = Layer::New();
633 #ifdef DECORATOR_DEBUG
634 mActiveLayer.SetName ( "ActiveLayerActor" );
637 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER );
638 mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
640 // Add the active layer telling the controller it doesn't need clipping.
641 mController.AddDecoration( mActiveLayer, false );
644 mActiveLayer.RaiseToTop();
647 void SetSelectionHandleMarkerSize( HandleImpl& handle )
649 if( handle.markerActor )
651 handle.markerActor.SetSize( 0, handle.lineHeight );
655 void CreateGrabHandle()
657 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
658 if( !grabHandle.actor )
660 if( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] )
662 grabHandle.actor = ImageView::New( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] );
663 GetImpl( grabHandle.actor).SetDepthIndex( DepthIndex::DECORATION );
664 grabHandle.actor.SetAnchorPoint( AnchorPoint::TOP_CENTER );
666 // Area that Grab handle responds to, larger than actual handle so easier to move
667 #ifdef DECORATOR_DEBUG
668 grabHandle.actor.SetName( "GrabHandleActor" );
669 if ( Dali::Internal::gLogFilter->IsEnabledFor( Debug::Verbose ) )
671 grabHandle.grabArea = Control::New();
672 Toolkit::Control control = Toolkit::Control::DownCast( grabHandle.grabArea );
673 control.SetBackgroundColor( Vector4( 1.0f, 1.0f, 1.0f, 0.5f ) );
674 grabHandle.grabArea.SetName( "GrabArea" );
678 grabHandle.grabArea = Actor::New();
679 grabHandle.grabArea.SetName( "GrabArea" );
682 grabHandle.grabArea = Actor::New();
685 grabHandle.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
686 grabHandle.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
687 grabHandle.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
688 grabHandle.grabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
689 grabHandle.actor.Add( grabHandle.grabArea );
690 grabHandle.actor.SetColor( mHandleColor );
692 grabHandle.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnGrabHandleTouched );
693 mTapDetector.Attach( grabHandle.grabArea );
694 mPanGestureDetector.Attach( grabHandle.grabArea );
696 mActiveLayer.Add( grabHandle.actor );
700 if( grabHandle.actor && !grabHandle.actor.GetParent() )
702 mActiveLayer.Add( grabHandle.actor );
706 void CreateHandleMarker( HandleImpl& handle, Image& image, HandleType handleType )
710 handle.markerActor = ImageView::New( image );
711 handle.markerActor.SetColor( mHandleColor );
712 handle.actor.Add( handle.markerActor );
714 handle.markerActor.SetResizePolicy ( ResizePolicy::FIXED, Dimension::HEIGHT );
716 if( LEFT_SELECTION_HANDLE == handleType )
718 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_RIGHT );
719 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_RIGHT );
721 else if( RIGHT_SELECTION_HANDLE == handleType )
723 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_LEFT );
724 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
729 void CreateSelectionHandles()
731 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
734 if( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
736 primary.actor = ImageView::New( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
737 #ifdef DECORATOR_DEBUG
738 primary.actor.SetName("SelectionHandleOne");
740 primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
741 GetImpl( primary.actor ).SetDepthIndex( DepthIndex::DECORATION );
742 primary.actor.SetColor( mHandleColor );
744 primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
745 #ifdef DECORATOR_DEBUG
746 primary.grabArea.SetName("SelectionHandleOneGrabArea");
748 primary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
749 primary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
750 primary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
751 primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
753 mTapDetector.Attach( primary.grabArea );
754 mPanGestureDetector.Attach( primary.grabArea );
755 primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched );
757 primary.actor.Add( primary.grabArea );
759 CreateHandleMarker( primary, mHandleImages[LEFT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], LEFT_SELECTION_HANDLE );
763 if( primary.actor && !primary.actor.GetParent() )
765 mActiveLayer.Add( primary.actor );
768 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
769 if( !secondary.actor )
771 if( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
773 secondary.actor = ImageView::New( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
774 #ifdef DECORATOR_DEBUG
775 secondary.actor.SetName("SelectionHandleTwo");
777 secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text.
778 GetImpl( secondary.actor ).SetDepthIndex( DepthIndex::DECORATION );
779 secondary.actor.SetColor( mHandleColor );
781 secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
782 #ifdef DECORATOR_DEBUG
783 secondary.grabArea.SetName("SelectionHandleTwoGrabArea");
785 secondary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
786 secondary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
787 secondary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
788 secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
790 mTapDetector.Attach( secondary.grabArea );
791 mPanGestureDetector.Attach( secondary.grabArea );
792 secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched );
794 secondary.actor.Add( secondary.grabArea );
796 CreateHandleMarker( secondary, mHandleImages[RIGHT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], RIGHT_SELECTION_HANDLE );
800 if( secondary.actor && !secondary.actor.GetParent() )
802 mActiveLayer.Add( secondary.actor );
806 void CalculateHandleWorldCoordinates( HandleImpl& handle, Vector2& position )
808 // Gets the world position of the active layer. The active layer is where the handles are added.
809 const Vector3 parentWorldPosition = mActiveLayer.GetCurrentWorldPosition();
811 // The grab handle position in world coords.
812 // The active layer's world position is the center of the active layer. The origin of the
813 // coord system of the handles is the top left of the active layer.
814 position.x = parentWorldPosition.x - 0.5f * mControlSize.width + handle.position.x;
815 position.y = parentWorldPosition.y - 0.5f * mControlSize.height + handle.position.y;
818 void SetGrabHandlePosition()
820 // Reference to the grab handle.
821 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
823 // Transforms the handle position into world coordinates.
824 // @note This is not the same value as grabHandle.actor.GetCurrentWorldPosition()
825 // as it's transforming the handle's position set by the text-controller and not
826 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
827 // retrieves the position of the center of the actor but the handle's position set
828 // by the text controller is not the center of the actor.
829 Vector2 grabHandleWorldPosition;
830 CalculateHandleWorldCoordinates( grabHandle, grabHandleWorldPosition );
832 // Check if the grab handle exceeds the boundaries of the decoration box.
833 // At the moment only the height is checked for the grab handle.
835 grabHandle.verticallyFlipped = ( grabHandle.verticallyFlippedPreferred &&
836 ( ( grabHandleWorldPosition.y - grabHandle.size.height ) > mBoundingBox.y ) ) ||
837 ( grabHandleWorldPosition.y + grabHandle.lineHeight + grabHandle.size.height > mBoundingBox.w );
839 // The grab handle 'y' position in local coords.
840 // If the grab handle exceeds the bottom of the decoration box,
841 // set the 'y' position to the top of the line.
842 // The SetGrabHandleImage() method will change the orientation.
843 const float yLocalPosition = grabHandle.verticallyFlipped ? grabHandle.position.y : grabHandle.position.y + grabHandle.lineHeight;
845 if( grabHandle.actor )
847 grabHandle.actor.SetPosition( grabHandle.position.x + floor( 0.5f * mCursorWidth ),
848 yLocalPosition ); // TODO : Fix for multiline.
852 void SetSelectionHandlePosition( HandleType type )
854 const bool isPrimaryHandle = LEFT_SELECTION_HANDLE == type;
856 // Reference to the selection handle.
857 HandleImpl& handle = mHandle[type];
859 // Transforms the handle position into world coordinates.
860 // @note This is not the same value as handle.actor.GetCurrentWorldPosition()
861 // as it's transforming the handle's position set by the text-controller and not
862 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
863 // retrieves the position of the center of the actor but the handle's position set
864 // by the text controller is not the center of the actor.
865 Vector2 handleWorldPosition;
866 CalculateHandleWorldCoordinates( handle, handleWorldPosition );
868 // Whether to flip the handle (horizontally).
869 bool flipHandle = isPrimaryHandle ? mFlipLeftSelectionHandleDirection : mFlipRightSelectionHandleDirection;
871 // Whether to flip the handles if they are crossed.
872 bool crossFlip = false;
873 if( mFlipSelectionHandlesOnCross || !mHandlePanning )
875 crossFlip = mHandleCurrentCrossed;
878 // Does not flip if both conditions are true (double flip)
879 flipHandle = flipHandle != ( crossFlip || mHandlePreviousCrossed );
881 // Will flip the handles vertically if the user prefers it.
882 bool verticallyFlippedPreferred = handle.verticallyFlippedPreferred;
884 if( crossFlip || mHandlePreviousCrossed )
886 if( isPrimaryHandle )
888 verticallyFlippedPreferred = mHandle[RIGHT_SELECTION_HANDLE].verticallyFlippedPreferred;
892 verticallyFlippedPreferred = mHandle[LEFT_SELECTION_HANDLE].verticallyFlippedPreferred;
896 // Check if the selection handle exceeds the boundaries of the decoration box.
897 const bool exceedsLeftEdge = ( isPrimaryHandle ? !flipHandle : flipHandle ) && ( handleWorldPosition.x - handle.size.width < mBoundingBox.x );
898 const bool exceedsRightEdge = ( isPrimaryHandle ? flipHandle : !flipHandle ) && ( handleWorldPosition.x + handle.size.width > mBoundingBox.z );
900 // Does not flip if both conditions are true (double flip)
901 flipHandle = flipHandle != ( exceedsLeftEdge || exceedsRightEdge );
905 if( handle.actor && !handle.horizontallyFlipped )
907 // Change the anchor point to flip the image.
908 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_LEFT : AnchorPoint::TOP_RIGHT );
910 handle.horizontallyFlipped = true;
915 if( handle.actor && handle.horizontallyFlipped )
917 // Reset the anchor point.
918 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_RIGHT : AnchorPoint::TOP_LEFT );
920 handle.horizontallyFlipped = false;
924 // Whether to flip the handle vertically.
925 handle.verticallyFlipped = ( verticallyFlippedPreferred &&
926 ( ( handleWorldPosition.y - handle.size.height ) > mBoundingBox.y ) ) ||
927 ( handleWorldPosition.y + handle.lineHeight + handle.size.height > mBoundingBox.w );
929 // The primary selection handle 'y' position in local coords.
930 // If the handle exceeds the bottom of the decoration box,
931 // set the 'y' position to the top of the line.
932 // The SetHandleImage() method will change the orientation.
933 const float yLocalPosition = handle.verticallyFlipped ? handle.position.y : handle.position.y + handle.lineHeight;
937 handle.actor.SetPosition( handle.position.x,
938 yLocalPosition ); // TODO : Fix for multiline.
942 void SetHandleImage( HandleType type )
944 HandleImpl& handle = mHandle[type];
946 HandleType markerType = HANDLE_TYPE_COUNT;
947 // If the selection handle is flipped it chooses the image of the other selection handle. Does nothing for the grab handle.
948 if( LEFT_SELECTION_HANDLE == type )
950 type = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE;
951 markerType = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE_MARKER : LEFT_SELECTION_HANDLE_MARKER;
953 else if( RIGHT_SELECTION_HANDLE == type )
955 type = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE : RIGHT_SELECTION_HANDLE;
956 markerType = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE_MARKER : RIGHT_SELECTION_HANDLE_MARKER;
959 // Chooses between the released or pressed image. It checks whether the pressed image exists.
962 const HandleImageType imageType = ( handle.pressed ? ( mHandleImages[type][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
964 handle.actor.SetImage( mHandleImages[type][imageType] );
967 if( HANDLE_TYPE_COUNT != markerType )
969 if( handle.markerActor )
971 const HandleImageType markerImageType = ( handle.pressed ? ( mHandleImages[markerType][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
972 handle.markerActor.SetImage( mHandleImages[markerType][markerImageType] );
976 // Whether to flip the handle vertically.
979 handle.actor.SetOrientation( handle.verticallyFlipped ? ANGLE_180 : ANGLE_0, Vector3::XAXIS );
983 void CreateHighlight()
985 if( !mHighlightActor )
987 mHighlightActor = Actor::New();
989 #ifdef DECORATOR_DEBUG
990 mHighlightActor.SetName( "HighlightActor" );
992 mHighlightActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
993 mHighlightActor.SetSize( 1.0f, 1.0f );
994 mHighlightActor.SetColor( mHighlightColor );
995 mHighlightActor.SetColorMode( USE_OWN_COLOR );
998 // Add the highlight box telling the controller it needs clipping.
999 mController.AddDecoration( mHighlightActor, true );
1002 void UpdateHighlight()
1004 if ( mHighlightActor )
1006 if( !mHighlightQuadList.empty() )
1008 Vector< Vector2 > vertices;
1009 Vector< unsigned int> indices;
1012 std::vector<QuadCoordinates>::iterator iter = mHighlightQuadList.begin();
1013 std::vector<QuadCoordinates>::iterator endIter = mHighlightQuadList.end();
1015 for( std::size_t v = 0; iter != endIter; ++iter,v+=4 )
1017 QuadCoordinates& quad = *iter;
1020 vertex.x = quad.min.x;
1021 vertex.y = quad.min.y;
1022 vertices.PushBack( vertex );
1025 vertex.x = quad.max.x;
1026 vertex.y = quad.min.y;
1027 vertices.PushBack( vertex );
1029 // bottom-left (v+2)
1030 vertex.x = quad.min.x;
1031 vertex.y = quad.max.y;
1032 vertices.PushBack( vertex );
1034 // bottom-right (v+3)
1035 vertex.x = quad.max.x;
1036 vertex.y = quad.max.y;
1037 vertices.PushBack( vertex );
1039 // triangle A (3, 1, 0)
1040 indices.PushBack( v + 3 );
1041 indices.PushBack( v + 1 );
1042 indices.PushBack( v );
1044 // triangle B (0, 2, 3)
1045 indices.PushBack( v );
1046 indices.PushBack( v + 2 );
1047 indices.PushBack( v + 3 );
1050 if( ! mQuadVertices )
1052 mQuadVertices = PropertyBuffer::New( mQuadVertexFormat );
1055 if( ! mQuadIndices )
1057 mQuadIndices = PropertyBuffer::New( mQuadIndexFormat );
1060 mQuadVertices.SetData( &vertices[ 0 ], vertices.Size() );
1061 mQuadIndices.SetData( &indices[ 0 ], indices.Size() );
1063 if( !mQuadGeometry )
1065 mQuadGeometry = Geometry::New();
1066 mQuadGeometry.AddVertexBuffer( mQuadVertices );
1068 mQuadGeometry.SetIndexBuffer( mQuadIndices );
1070 if( !mHighlightRenderer )
1072 mHighlightRenderer = Dali::Renderer::New( mQuadGeometry, mHighlightMaterial );
1073 mHighlightActor.AddRenderer( mHighlightRenderer );
1077 mHighlightActor.SetPosition( mHighlightPosition.x,
1078 mHighlightPosition.y );
1080 mHighlightQuadList.clear();
1082 if( mHighlightRenderer )
1084 mHighlightRenderer.SetProperty( Renderer::Property::DEPTH_INDEX, mTextDepth - 2 ); // text is rendered at mTextDepth and text's shadow at mTextDepth -1u.
1089 void OnTap( Actor actor, const TapGesture& tap )
1091 if( actor == mHandle[GRAB_HANDLE].actor )
1097 void DoPan( HandleImpl& handle, HandleType type, const PanGesture& gesture )
1099 if( Gesture::Started == gesture.state )
1101 handle.grabDisplacementX = handle.grabDisplacementY = 0;
1104 handle.grabDisplacementX += gesture.displacement.x;
1105 handle.grabDisplacementY += gesture.displacement.y;
1107 const float x = handle.position.x + handle.grabDisplacementX;
1108 const float y = handle.position.y + handle.lineHeight*0.5f + handle.grabDisplacementY;
1110 if( Gesture::Started == gesture.state ||
1111 Gesture::Continuing == gesture.state )
1114 mController.GetTargetSize( targetSize );
1116 if( x < mScrollThreshold )
1118 mScrollDirection = SCROLL_RIGHT;
1119 mHandleScrolling = type;
1122 else if( x > targetSize.width - mScrollThreshold )
1124 mScrollDirection = SCROLL_LEFT;
1125 mHandleScrolling = type;
1130 mHandleScrolling = HANDLE_TYPE_COUNT;
1132 mController.DecorationEvent( type, HANDLE_PRESSED, x, y );
1135 mHandlePanning = true;
1137 else if( Gesture::Finished == gesture.state ||
1138 Gesture::Cancelled == gesture.state )
1141 ( mScrollTimer.IsRunning() || mNotifyEndOfScroll ) )
1143 mNotifyEndOfScroll = false;
1144 mHandleScrolling = HANDLE_TYPE_COUNT;
1146 mController.DecorationEvent( type, HANDLE_STOP_SCROLLING, x, y );
1150 mController.DecorationEvent( type, HANDLE_RELEASED, x, y );
1155 handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_RELEASED] );
1157 handle.pressed = false;
1159 mHandlePanning = false;
1163 void OnPan( Actor actor, const PanGesture& gesture )
1165 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1166 HandleImpl& primarySelectionHandle = mHandle[LEFT_SELECTION_HANDLE];
1167 HandleImpl& secondarySelectionHandle = mHandle[RIGHT_SELECTION_HANDLE];
1169 if( actor == grabHandle.grabArea )
1171 DoPan( grabHandle, GRAB_HANDLE, gesture );
1173 else if( actor == primarySelectionHandle.grabArea )
1175 DoPan( primarySelectionHandle, LEFT_SELECTION_HANDLE, gesture );
1177 else if( actor == secondarySelectionHandle.grabArea )
1179 DoPan( secondarySelectionHandle, RIGHT_SELECTION_HANDLE, gesture );
1183 bool OnGrabHandleTouched( Actor actor, const TouchEvent& event )
1185 // Switch between pressed/release grab-handle images
1186 if( event.GetPointCount() > 0 &&
1187 mHandle[GRAB_HANDLE].actor )
1189 const TouchPoint& point = event.GetPoint(0);
1191 if( TouchPoint::Down == point.state )
1193 mHandle[GRAB_HANDLE].pressed = true;
1195 else if( ( TouchPoint::Up == point.state ) ||
1196 ( TouchPoint::Interrupted == point.state ) )
1198 mHandle[GRAB_HANDLE].pressed = false;
1201 SetHandleImage( GRAB_HANDLE );
1204 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1208 bool OnHandleOneTouched( Actor actor, const TouchEvent& event )
1210 // Switch between pressed/release selection handle images
1211 if( event.GetPointCount() > 0 &&
1212 mHandle[LEFT_SELECTION_HANDLE].actor )
1214 const TouchPoint& point = event.GetPoint(0);
1216 if( TouchPoint::Down == point.state )
1218 mHandle[LEFT_SELECTION_HANDLE].pressed = true;
1220 else if( ( TouchPoint::Up == point.state ) ||
1221 ( TouchPoint::Interrupted == point.state ) )
1223 mHandle[LEFT_SELECTION_HANDLE].pressed = false;
1224 mHandlePreviousCrossed = mHandleCurrentCrossed;
1225 mHandlePanning = false;
1228 SetHandleImage( LEFT_SELECTION_HANDLE );
1231 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1235 bool OnHandleTwoTouched( Actor actor, const TouchEvent& event )
1237 // Switch between pressed/release selection handle images
1238 if( event.GetPointCount() > 0 &&
1239 mHandle[RIGHT_SELECTION_HANDLE].actor )
1241 const TouchPoint& point = event.GetPoint(0);
1243 if( TouchPoint::Down == point.state )
1245 mHandle[RIGHT_SELECTION_HANDLE].pressed = true;
1247 else if( ( TouchPoint::Up == point.state ) ||
1248 ( TouchPoint::Interrupted == point.state ) )
1250 mHandle[RIGHT_SELECTION_HANDLE].pressed = false;
1251 mHandlePreviousCrossed = mHandleCurrentCrossed;
1252 mHandlePanning = false;
1255 SetHandleImage( RIGHT_SELECTION_HANDLE );
1258 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1262 void HandleResetPosition( PropertyNotification& source )
1264 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1266 if( grabHandle.active )
1268 // Sets the grab handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1269 SetGrabHandlePosition();
1271 // Sets the grab handle image according if it's pressed, flipped, etc.
1272 SetHandleImage( GRAB_HANDLE );
1276 // Sets the primary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1277 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
1279 // Sets the primary handle image according if it's pressed, flipped, etc.
1280 SetHandleImage( LEFT_SELECTION_HANDLE );
1282 // Sets the secondary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1283 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
1285 // Sets the secondary handle image according if it's pressed, flipped, etc.
1286 SetHandleImage( RIGHT_SELECTION_HANDLE );
1290 void SetupActiveLayerPropertyNotifications()
1297 // Vertical notifications.
1299 // Disconnect any previous connected callback.
1300 if( mVerticalLessThanNotification )
1302 mVerticalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1303 mActiveLayer.RemovePropertyNotification( mVerticalLessThanNotification );
1306 if( mVerticalGreaterThanNotification )
1308 mVerticalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1309 mActiveLayer.RemovePropertyNotification( mVerticalGreaterThanNotification );
1312 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1313 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1314 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1316 if( grabHandle.active )
1318 if( grabHandle.verticallyFlipped )
1320 // The grab handle is vertically flipped. Never is going to exceed the bottom edje of the display.
1321 mVerticalGreaterThanNotification.Reset();
1323 // The vertical distance from the center of the active layer to the top edje of the display.
1324 const float topHeight = 0.5f * mControlSize.height - grabHandle.position.y + grabHandle.size.height;
1326 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1327 LessThanCondition( mBoundingBox.y + topHeight ) );
1329 // Notifies the change from false to true and from true to false.
1330 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1332 // Connects the signals with the callbacks.
1333 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1337 // The grab handle is not vertically flipped. Never is going to exceed the top edje of the display.
1338 mVerticalLessThanNotification.Reset();
1340 // The vertical distance from the center of the active layer to the bottom edje of the display.
1341 const float bottomHeight = -0.5f * mControlSize.height + grabHandle.position.y + grabHandle.lineHeight + grabHandle.size.height;
1343 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1344 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1346 // Notifies the change from false to true and from true to false.
1347 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1349 // Connects the signals with the callbacks.
1350 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1353 else // The selection handles are active
1355 if( primaryHandle.verticallyFlipped && secondaryHandle.verticallyFlipped )
1357 // Both selection handles are vertically flipped. Never are going to exceed the bottom edje of the display.
1358 mVerticalGreaterThanNotification.Reset();
1360 // The vertical distance from the center of the active layer to the top edje of the display.
1361 const float topHeight = 0.5f * mControlSize.height + std::max( -primaryHandle.position.y + primaryHandle.size.height, -secondaryHandle.position.y + secondaryHandle.size.height );
1363 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1364 LessThanCondition( mBoundingBox.y + topHeight ) );
1366 // Notifies the change from false to true and from true to false.
1367 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1369 // Connects the signals with the callbacks.
1370 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1372 else if( !primaryHandle.verticallyFlipped && !secondaryHandle.verticallyFlipped )
1374 // Both selection handles aren't vertically flipped. Never are going to exceed the top edje of the display.
1375 mVerticalLessThanNotification.Reset();
1377 // The vertical distance from the center of the active layer to the bottom edje of the display.
1378 const float bottomHeight = -0.5f * mControlSize.height + std::max( primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height,
1379 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height );
1381 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1382 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1384 // Notifies the change from false to true and from true to false.
1385 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1387 // Connects the signals with the callbacks.
1388 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1392 // Only one of the selection handles is vertically flipped. Both vertical notifications are needed.
1394 // The vertical distance from the center of the active layer to the top edje of the display.
1395 const float topHeight = 0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1396 -primaryHandle.position.y + primaryHandle.size.height :
1397 -secondaryHandle.position.y + secondaryHandle.size.height );
1399 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1400 LessThanCondition( mBoundingBox.y + topHeight ) );
1402 // Notifies the change from false to true and from true to false.
1403 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1405 // Connects the signals with the callbacks.
1406 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1408 // The vertical distance from the center of the active layer to the bottom edje of the display.
1409 const float bottomHeight = -0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1410 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height :
1411 primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height );
1413 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1414 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1416 // Notifies the change from false to true and from true to false.
1417 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1419 // Connects the signals with the callbacks.
1420 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1424 // Horizontal notifications.
1426 // Disconnect any previous connected callback.
1427 if( mHorizontalLessThanNotification )
1429 mHorizontalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1430 mActiveLayer.RemovePropertyNotification( mHorizontalLessThanNotification );
1433 if( mHorizontalGreaterThanNotification )
1435 mHorizontalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1436 mActiveLayer.RemovePropertyNotification( mHorizontalGreaterThanNotification );
1439 if( primaryHandle.active || secondaryHandle.active )
1441 // The horizontal distance from the center of the active layer to the left edje of the display.
1442 const float leftWidth = 0.5f * mControlSize.width + std::max( -primaryHandle.position.x + primaryHandle.size.width,
1443 -secondaryHandle.position.x + secondaryHandle.size.width );
1445 mHorizontalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1446 LessThanCondition( mBoundingBox.x + leftWidth ) );
1448 // Notifies the change from false to true and from true to false.
1449 mHorizontalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1451 // Connects the signals with the callbacks.
1452 mHorizontalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1454 // The horizontal distance from the center of the active layer to the right edje of the display.
1455 const float rightWidth = -0.5f * mControlSize.width + std::max( primaryHandle.position.x + primaryHandle.size.width,
1456 secondaryHandle.position.x + secondaryHandle.size.width );
1458 mHorizontalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1459 GreaterThanCondition( mBoundingBox.z - rightWidth ) );
1461 // Notifies the change from false to true and from true to false.
1462 mHorizontalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1464 // Connects the signals with the callbacks.
1465 mHorizontalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1471 float AlternatePopUpPositionRelativeToCursor()
1473 float alternativePosition = 0.0f;
1475 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1477 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1478 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1479 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1480 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
1482 if( primaryHandle.active || secondaryHandle.active )
1484 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
1485 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
1489 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + grabHandle.size.height + cursor.position.y;
1492 return alternativePosition;
1495 void PopUpLeavesVerticalBoundary( PropertyNotification& source )
1497 float alternativeYPosition = 0.0f;
1498 // todo use AlternatePopUpPositionRelativeToSelectionHandles() if text is highlighted
1499 // if can't be positioned above, then position below row.
1500 alternativeYPosition = AlternatePopUpPositionRelativeToCursor();
1502 mCopyPastePopup.actor.SetY( alternativeYPosition );
1505 void SetUpPopupPositionNotifications()
1507 // Note Property notifications ignore any set anchor point so conditions must allow for this. Default is Top Left.
1509 // Exceeding vertical boundary
1511 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1513 PropertyNotification verticalExceedNotification = mCopyPastePopup.actor.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1514 OutsideCondition( mBoundingBox.y + popupHeight * 0.5f,
1515 mBoundingBox.w - popupHeight * 0.5f ) );
1517 verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary );
1520 void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, const Vector3& popupDistanceFromAnchorPoint, Actor parent, const Vector4& boundingRectangleWorld )
1522 DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() )
1524 // Parent must already by added to Stage for these Get calls to work
1525 const Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize() * parent.GetCurrentAnchorPoint();
1526 const Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition; // Parent World position plus popup local position gives World Position
1528 // Calculate distance to move popup (in local space) so fits within the boundary
1529 float xOffSetToKeepWithinBounds = 0.0f;
1530 if( popupWorldPosition.x - popupDistanceFromAnchorPoint.x < boundingRectangleWorld.x )
1532 xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x );
1534 else if( popupWorldPosition.x + popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z )
1536 xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x + popupDistanceFromAnchorPoint.x );
1539 // Ensure initial display of Popup is in alternative position if can not fit above. As Property notification will be a frame behind.
1540 if( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y )
1542 requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor();
1545 requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds;
1547 // Prevent pixel mis-alignment by rounding down.
1548 requiredPopupPosition.x = floor( requiredPopupPosition.x );
1549 requiredPopupPosition.y = floor( requiredPopupPosition.y );
1552 void SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1554 HandleImpl& handle = mHandle[handleType];
1555 handle.size = Size( image.GetWidth(), image.GetHeight() );
1557 mHandleImages[handleType][handleImageType] = image;
1560 void SetScrollThreshold( float threshold )
1562 mScrollThreshold = threshold;
1565 float GetScrollThreshold() const
1567 return mScrollThreshold;
1570 void SetScrollSpeed( float speed )
1572 mScrollSpeed = speed;
1573 mScrollDistance = speed * SCROLL_TICK_INTERVAL * TO_SECONDS;
1576 float GetScrollSpeed() const
1578 return mScrollSpeed;
1581 void NotifyEndOfScroll()
1587 mNotifyEndOfScroll = true;
1592 * Creates and starts a timer to scroll the text when handles are close to the edges of the text.
1594 * It only starts the timer if it's already created.
1596 void StartScrollTimer()
1600 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1601 mScrollTimer.TickSignal().Connect( this, &Decorator::Impl::OnScrollTimerTick );
1604 if( !mScrollTimer.IsRunning() )
1606 mScrollTimer.Start();
1611 * Stops the timer used to scroll the text.
1613 void StopScrollTimer()
1617 mScrollTimer.Stop();
1622 * Callback called by the timer used to scroll the text.
1624 * It calculates and sets a new scroll position.
1626 bool OnScrollTimerTick()
1628 if( HANDLE_TYPE_COUNT != mHandleScrolling )
1630 mController.DecorationEvent( mHandleScrolling,
1632 mScrollDirection == SCROLL_RIGHT ? mScrollDistance : -mScrollDistance,
1639 ControllerInterface& mController;
1641 TapGestureDetector mTapDetector;
1642 PanGestureDetector mPanGestureDetector;
1643 Timer mCursorBlinkTimer; ///< Timer to signal cursor to blink
1644 Timer mScrollTimer; ///< Timer used to scroll the text when the grab handle is moved close to the edges.
1646 Layer mActiveLayer; ///< Layer for active handles and alike that ensures they are above all else.
1647 PropertyNotification mVerticalLessThanNotification; ///< Notifies when the 'y' coord of the active layer is less than a given value.
1648 PropertyNotification mVerticalGreaterThanNotification; ///< Notifies when the 'y' coord of the active layer is grater than a given value.
1649 PropertyNotification mHorizontalLessThanNotification; ///< Notifies when the 'x' coord of the active layer is less than a given value.
1650 PropertyNotification mHorizontalGreaterThanNotification; ///< Notifies when the 'x' coord of the active layer is grater than a given value.
1651 Control mPrimaryCursor;
1652 Control mSecondaryCursor;
1654 Actor mHighlightActor; ///< Actor to display highlight
1655 Renderer mHighlightRenderer;
1656 Material mHighlightMaterial; ///< Material used for highlight
1657 Property::Map mQuadVertexFormat;
1658 Property::Map mQuadIndexFormat;
1659 PopupImpl mCopyPastePopup;
1660 TextSelectionPopup::Buttons mEnabledPopupButtons; /// Bit mask of currently enabled Popup buttons
1661 TextSelectionPopupCallbackInterface& mTextSelectionPopupCallbackInterface;
1663 Image mHandleImages[HANDLE_TYPE_COUNT][HANDLE_IMAGE_TYPE_COUNT];
1664 Vector4 mHandleColor;
1666 CursorImpl mCursor[CURSOR_COUNT];
1667 HandleImpl mHandle[HANDLE_TYPE_COUNT];
1669 PropertyBuffer mQuadVertices;
1670 PropertyBuffer mQuadIndices;
1671 Geometry mQuadGeometry;
1672 QuadContainer mHighlightQuadList; ///< Sub-selections that combine to create the complete selection highlight
1674 Vector4 mBoundingBox; ///< The bounding box in world coords.
1675 Vector4 mHighlightColor; ///< Color of the highlight
1676 Vector2 mHighlightPosition; ///< The position of the highlight actor.
1677 Vector2 mControlSize; ///< The control's size. Set by the Relayout.
1679 unsigned int mActiveCursor;
1680 unsigned int mCursorBlinkInterval;
1681 float mCursorBlinkDuration;
1682 float mCursorWidth; ///< The width of the cursors in pixels.
1683 HandleType mHandleScrolling; ///< The handle which is scrolling.
1684 ScrollDirection mScrollDirection; ///< The direction of the scroll.
1685 float mScrollThreshold; ///< Defines a square area inside the control, close to the edge. A cursor entering this area will trigger scroll events.
1686 float mScrollSpeed; ///< The scroll speed in pixels per second.
1687 float mScrollDistance; ///< Distance the text scrolls during a scroll interval.
1688 int mTextDepth; ///< The depth used to render the text.
1690 bool mActiveCopyPastePopup : 1;
1691 bool mPopupSetNewPosition : 1;
1692 bool mCursorBlinkStatus : 1; ///< Flag to switch between blink on and blink off.
1693 bool mDelayCursorBlink : 1; ///< Used to avoid cursor blinking when entering text.
1694 bool mPrimaryCursorVisible : 1; ///< Whether the primary cursor is visible.
1695 bool mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible.
1696 bool mFlipSelectionHandlesOnCross : 1; ///< Whether to flip the selection handles as soon as they cross.
1697 bool mFlipLeftSelectionHandleDirection : 1; ///< Whether to flip the left selection handle image because of the character's direction.
1698 bool mFlipRightSelectionHandleDirection : 1; ///< Whether to flip the right selection handle image because of the character's direction.
1699 bool mHandlePanning : 1; ///< Whether any of the handles is moving.
1700 bool mHandleCurrentCrossed : 1; ///< Whether the handles are crossed.
1701 bool mHandlePreviousCrossed : 1; ///< Whether the handles where crossed at the last handle touch up.
1702 bool mNotifyEndOfScroll : 1; ///< Whether to notify the end of the scroll.
1705 DecoratorPtr Decorator::New( ControllerInterface& controller,
1706 TextSelectionPopupCallbackInterface& callbackInterface )
1708 return DecoratorPtr( new Decorator( controller,
1709 callbackInterface ) );
1712 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
1714 LocalToWorldCoordinatesBoundingBox( boundingBox, mImpl->mBoundingBox );
1717 void Decorator::GetBoundingBox( Rect<int>& boundingBox ) const
1719 WorldToLocalCoordinatesBoundingBox( mImpl->mBoundingBox, boundingBox );
1722 void Decorator::Relayout( const Vector2& size )
1724 mImpl->Relayout( size );
1727 void Decorator::UpdatePositions( const Vector2& scrollOffset )
1729 mImpl->UpdatePositions( scrollOffset );
1734 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
1736 mImpl->mActiveCursor = activeCursor;
1739 unsigned int Decorator::GetActiveCursor() const
1741 return mImpl->mActiveCursor;
1744 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
1746 mImpl->mCursor[cursor].position.x = x;
1747 mImpl->mCursor[cursor].position.y = y;
1748 mImpl->mCursor[cursor].cursorHeight = cursorHeight;
1749 mImpl->mCursor[cursor].lineHeight = lineHeight;
1752 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
1754 x = mImpl->mCursor[cursor].position.x;
1755 y = mImpl->mCursor[cursor].position.y;
1756 cursorHeight = mImpl->mCursor[cursor].cursorHeight;
1757 lineHeight = mImpl->mCursor[cursor].lineHeight;
1760 const Vector2& Decorator::GetPosition( Cursor cursor ) const
1762 return mImpl->mCursor[cursor].position;
1765 void Decorator::SetCursorColor( Cursor cursor, const Dali::Vector4& color )
1767 mImpl->mCursor[cursor].color = color;
1770 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
1772 return mImpl->mCursor[cursor].color;
1775 void Decorator::StartCursorBlink()
1777 if ( !mImpl->mCursorBlinkTimer )
1779 mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
1780 mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
1783 if ( !mImpl->mCursorBlinkTimer.IsRunning() )
1785 mImpl->mCursorBlinkTimer.Start();
1789 void Decorator::StopCursorBlink()
1791 if ( mImpl->mCursorBlinkTimer )
1793 mImpl->mCursorBlinkTimer.Stop();
1796 mImpl->mCursorBlinkStatus = true; // Keep cursor permanently shown
1799 void Decorator::DelayCursorBlink()
1801 mImpl->mCursorBlinkStatus = true; // Show cursor for a bit longer
1802 mImpl->mDelayCursorBlink = true;
1805 void Decorator::SetCursorBlinkInterval( float seconds )
1807 mImpl->mCursorBlinkInterval = static_cast<unsigned int>( seconds * TO_MILLISECONDS ); // Convert to milliseconds
1810 float Decorator::GetCursorBlinkInterval() const
1812 return static_cast<float>( mImpl->mCursorBlinkInterval ) * TO_SECONDS;
1815 void Decorator::SetCursorBlinkDuration( float seconds )
1817 mImpl->mCursorBlinkDuration = seconds;
1820 float Decorator::GetCursorBlinkDuration() const
1822 return mImpl->mCursorBlinkDuration;
1825 void Decorator::SetCursorWidth( int width )
1827 mImpl->mCursorWidth = static_cast<float>( width );
1830 int Decorator::GetCursorWidth() const
1832 return static_cast<int>( mImpl->mCursorWidth );
1837 void Decorator::SetHandleActive( HandleType handleType, bool active )
1839 mImpl->mHandle[handleType].active = active;
1843 if( ( LEFT_SELECTION_HANDLE == handleType ) || ( RIGHT_SELECTION_HANDLE == handleType ) )
1845 mImpl->mHandlePreviousCrossed = false;
1848 // TODO: this is a work-around.
1849 // The problem is the handle actor does not receive the touch event with the Interrupt
1850 // state when the power button is pressed and the application goes to background.
1851 mImpl->mHandle[handleType].pressed = false;
1852 Image imageReleased = mImpl->mHandleImages[handleType][HANDLE_IMAGE_RELEASED];
1853 ImageView imageView = mImpl->mHandle[handleType].actor;
1854 if( imageReleased && imageView )
1856 imageView.SetImage( imageReleased );
1862 bool Decorator::IsHandleActive( HandleType handleType ) const
1864 return mImpl->mHandle[handleType].active ;
1867 void Decorator::SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1869 mImpl->SetHandleImage( handleType, handleImageType, image );
1872 Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType handleImageType ) const
1874 return mImpl->mHandleImages[handleType][handleImageType];
1877 void Decorator::SetHandleColor( const Vector4& color )
1879 mImpl->mHandleColor = color;
1882 const Vector4& Decorator::GetHandleColor() const
1884 return mImpl->mHandleColor;
1887 void Decorator::SetPosition( HandleType handleType, float x, float y, float height )
1889 // Adjust handle's displacement
1890 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1892 handle.grabDisplacementX -= x - handle.position.x;
1893 handle.grabDisplacementY -= y - handle.position.y;
1895 handle.position.x = x;
1896 handle.position.y = y;
1897 handle.lineHeight = height;
1900 void Decorator::GetPosition( HandleType handleType, float& x, float& y, float& height ) const
1902 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1904 x = handle.position.x;
1905 y = handle.position.y;
1906 height = handle.lineHeight;
1909 const Vector2& Decorator::GetPosition( HandleType handleType ) const
1911 return mImpl->mHandle[handleType].position;
1914 void Decorator::FlipHandleVertically( HandleType handleType, bool flip )
1916 mImpl->mHandle[handleType].verticallyFlippedPreferred = flip;
1919 bool Decorator::IsHandleVerticallyFlipped( HandleType handleType ) const
1921 return mImpl->mHandle[handleType].verticallyFlippedPreferred;
1924 void Decorator::FlipSelectionHandlesOnCrossEnabled( bool enable )
1926 mImpl->mFlipSelectionHandlesOnCross = enable;
1929 void Decorator::SetSelectionHandleFlipState( bool indicesSwapped, bool left, bool right )
1931 mImpl->mHandleCurrentCrossed = indicesSwapped;
1932 mImpl->mFlipLeftSelectionHandleDirection = left;
1933 mImpl->mFlipRightSelectionHandleDirection = right;
1936 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1938 mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1941 void Decorator::ClearHighlights()
1943 mImpl->mHighlightQuadList.clear();
1944 mImpl->mHighlightPosition = Vector2::ZERO;
1947 void Decorator::SetHighlightColor( const Vector4& color )
1949 mImpl->mHighlightColor = color;
1952 const Vector4& Decorator::GetHighlightColor() const
1954 return mImpl->mHighlightColor;
1957 void Decorator::SetTextDepth( int textDepth )
1959 mImpl->mTextDepth = textDepth;
1962 void Decorator::SetPopupActive( bool active )
1964 mImpl->mActiveCopyPastePopup = active;
1967 bool Decorator::IsPopupActive() const
1969 return mImpl->mActiveCopyPastePopup ;
1972 void Decorator::SetEnabledPopupButtons( TextSelectionPopup::Buttons& enabledButtonsBitMask )
1974 mImpl->mEnabledPopupButtons = enabledButtonsBitMask;
1976 if ( !mImpl->mCopyPastePopup.actor )
1978 mImpl->mCopyPastePopup.actor = TextSelectionPopup::New( &mImpl->mTextSelectionPopupCallbackInterface );
1979 #ifdef DECORATOR_DEBUG
1980 mImpl->mCopyPastePopup.actor.SetName("mCopyPastePopup");
1982 mImpl->mCopyPastePopup.actor.SetAnchorPoint( AnchorPoint::CENTER );
1983 mImpl->mCopyPastePopup.actor.OnRelayoutSignal().Connect( mImpl, &Decorator::Impl::PopupRelayoutComplete ); // Position popup after size negotiation
1986 mImpl->mCopyPastePopup.actor.EnableButtons( mImpl->mEnabledPopupButtons );
1989 TextSelectionPopup::Buttons& Decorator::GetEnabledPopupButtons()
1991 return mImpl->mEnabledPopupButtons;
1996 void Decorator::SetScrollThreshold( float threshold )
1998 mImpl->SetScrollThreshold( threshold );
2001 float Decorator::GetScrollThreshold() const
2003 return mImpl->GetScrollThreshold();
2006 void Decorator::SetScrollSpeed( float speed )
2008 mImpl->SetScrollSpeed( speed );
2011 float Decorator::GetScrollSpeed() const
2013 return mImpl->GetScrollSpeed();
2016 void Decorator::NotifyEndOfScroll()
2018 mImpl->NotifyEndOfScroll();
2021 Decorator::~Decorator()
2026 Decorator::Decorator( ControllerInterface& controller,
2027 TextSelectionPopupCallbackInterface& callbackInterface )
2030 mImpl = new Decorator::Impl( controller, callbackInterface );
2035 } // namespace Toolkit