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 )
299 // TODO - Remove this if nothing is active
302 // Show or hide the cursors
307 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
308 mPrimaryCursorVisible = ( cursor.position.x + mCursorWidth <= mControlSize.width ) && ( cursor.position.x >= 0.f );
309 if( mPrimaryCursorVisible )
311 mPrimaryCursor.SetPosition( cursor.position.x,
313 mPrimaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) );
315 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
317 if( mSecondaryCursor )
319 const CursorImpl& cursor = mCursor[SECONDARY_CURSOR];
320 mSecondaryCursorVisible = ( cursor.position.x + mCursorWidth <= mControlSize.width ) && ( cursor.position.x >= 0.f );
321 if( mSecondaryCursorVisible )
323 mSecondaryCursor.SetPosition( cursor.position.x,
325 mSecondaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) );
327 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
330 // Show or hide the grab handle
331 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
332 bool newGrabHandlePosition = false;
333 if( grabHandle.active )
335 const bool isVisible = ( grabHandle.position.x + floor( 0.5f * mCursorWidth ) <= mControlSize.width ) && ( grabHandle.position.x >= 0.f );
341 // Sets the grab handle position and calculate if it needs to be vertically flipped if it exceeds the boundary box.
342 SetGrabHandlePosition();
344 // Sets the grab handle image according if it's pressed, flipped, etc.
345 SetHandleImage( GRAB_HANDLE );
347 newGrabHandlePosition = true;
350 if( grabHandle.actor )
352 grabHandle.actor.SetVisible( isVisible );
355 else if( grabHandle.actor )
357 grabHandle.actor.Unparent();
360 // Show or hide the selection handles/highlight
361 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
362 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
363 bool newPrimaryHandlePosition = false;
364 bool newSecondaryHandlePosition = false;
365 if( primary.active || secondary.active )
367 const bool isPrimaryVisible = ( primary.position.x <= mControlSize.width ) && ( primary.position.x >= 0.f );
368 const bool isSecondaryVisible = ( secondary.position.x <= mControlSize.width ) && ( secondary.position.x >= 0.f );
370 if( isPrimaryVisible || isSecondaryVisible )
372 CreateSelectionHandles();
374 if( isPrimaryVisible )
376 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
378 // Sets the primary handle image according if it's pressed, flipped, etc.
379 SetHandleImage( LEFT_SELECTION_HANDLE );
381 SetSelectionHandleMarkerSize( primary );
383 newPrimaryHandlePosition = true;
386 if( isSecondaryVisible )
388 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
390 // Sets the secondary handle image according if it's pressed, flipped, etc.
391 SetHandleImage( RIGHT_SELECTION_HANDLE );
393 SetSelectionHandleMarkerSize( secondary );
395 newSecondaryHandlePosition = true;
401 primary.actor.SetVisible( isPrimaryVisible );
403 if( secondary.actor )
405 secondary.actor.SetVisible( isSecondaryVisible );
415 primary.actor.Unparent();
417 if( secondary.actor )
419 secondary.actor.Unparent();
421 if( mHighlightActor )
423 mHighlightActor.Unparent();
427 if( newGrabHandlePosition ||
428 newPrimaryHandlePosition ||
429 newSecondaryHandlePosition )
431 // Setup property notifications to find whether the handles leave the boundaries of the current display.
432 SetupActiveLayerPropertyNotifications();
435 if( mActiveCopyPastePopup )
441 if( mCopyPastePopup.actor )
443 mCopyPastePopup.actor.HidePopup();
444 mPopupSetNewPosition = true;
449 void UpdatePositions( const Vector2& scrollOffset )
451 mCursor[PRIMARY_CURSOR].position += scrollOffset;
452 mCursor[SECONDARY_CURSOR].position += scrollOffset;
453 mHandle[ GRAB_HANDLE ].position += scrollOffset;
454 mHandle[ LEFT_SELECTION_HANDLE ].position += scrollOffset;
455 mHandle[ RIGHT_SELECTION_HANDLE ].position += scrollOffset;
456 mHighlightPosition += scrollOffset;
461 if ( !mCopyPastePopup.actor )
466 if( !mCopyPastePopup.actor.GetParent() )
468 mActiveLayer.Add( mCopyPastePopup.actor );
471 mCopyPastePopup.actor.RaiseAbove( mActiveLayer );
472 mCopyPastePopup.actor.ShowPopup();
475 void DeterminePositionPopup()
477 if( !mActiveCopyPastePopup )
482 // Retrieves the popup's size after relayout.
483 const Vector3 popupSize = Vector3( mCopyPastePopup.actor.GetRelayoutSize( Dimension::WIDTH ), mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT ), 0.0f );
485 if( mPopupSetNewPosition )
487 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
488 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
489 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
490 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
492 if( primaryHandle.active || secondaryHandle.active )
494 // Calculates the popup's position if selection handles are active.
495 const float minHandleXPosition = std::min( primaryHandle.position.x, secondaryHandle.position.x );
496 const float maxHandleXPosition = std::max( primaryHandle.position.x, secondaryHandle.position.x );
497 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
499 mCopyPastePopup.position.x = minHandleXPosition + ( ( maxHandleXPosition - minHandleXPosition ) * 0.5f );
500 mCopyPastePopup.position.y = -0.5f * popupSize.height - maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
504 // Calculates the popup's position if the grab handle is active.
505 mCopyPastePopup.position = Vector3( cursor.position.x, -0.5f * popupSize.height - grabHandle.size.height + cursor.position.y, 0.0f );
509 // Checks if there is enough space above the text control. If not it places the popup under it.
510 GetConstrainedPopupPosition( mCopyPastePopup.position, popupSize * AnchorPoint::CENTER, mActiveLayer, mBoundingBox );
512 SetUpPopupPositionNotifications();
514 mCopyPastePopup.actor.SetPosition( mCopyPastePopup.position );
515 mPopupSetNewPosition = false;
518 void PopupRelayoutComplete( Actor actor )
520 // Size negotiation for CopyPastePopup complete so can get the size and constrain position within bounding box.
522 DeterminePositionPopup();
525 void CreateCursor( ImageActor& cursor, const Vector4& color )
527 cursor = CreateSolidColorActor( color );
528 cursor.SetSortModifier( DECORATION_DEPTH_INDEX );
529 cursor.SetParentOrigin( ParentOrigin::TOP_LEFT ); // Need to set the default parent origin as CreateSolidColorActor() sets a different one.
530 cursor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
533 // Add or Remove cursor(s) from parent
536 if( mActiveCursor == ACTIVE_CURSOR_NONE )
540 mPrimaryCursor.Unparent();
542 if( mSecondaryCursor )
544 mSecondaryCursor.Unparent();
549 // Create Primary and or Secondary Cursor(s) if active and add to parent
550 if ( mActiveCursor == ACTIVE_CURSOR_PRIMARY ||
551 mActiveCursor == ACTIVE_CURSOR_BOTH )
553 if ( !mPrimaryCursor )
555 CreateCursor( mPrimaryCursor, mCursor[PRIMARY_CURSOR].color );
556 #ifdef DECORATOR_DEBUG
557 mPrimaryCursor.SetName( "PrimaryCursorActor" );
561 if( !mPrimaryCursor.GetParent() )
563 mActiveLayer.Add( mPrimaryCursor );
567 if ( mActiveCursor == ACTIVE_CURSOR_BOTH )
569 if ( !mSecondaryCursor )
571 CreateCursor( mSecondaryCursor, mCursor[SECONDARY_CURSOR].color );
572 #ifdef DECORATOR_DEBUG
573 mSecondaryCursor.SetName( "SecondaryCursorActor" );
577 if( !mSecondaryCursor.GetParent() )
579 mActiveLayer.Add( mSecondaryCursor );
584 if( mSecondaryCursor )
586 mSecondaryCursor.Unparent();
592 bool OnCursorBlinkTimerTick()
594 if( !mDelayCursorBlink )
597 if ( mPrimaryCursor )
599 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
601 if ( mSecondaryCursor )
603 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
606 mCursorBlinkStatus = !mCursorBlinkStatus;
611 mDelayCursorBlink = false;
617 void SetupTouchEvents()
619 mTapDetector = TapGestureDetector::New();
620 mTapDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnTap );
622 mPanGestureDetector = PanGestureDetector::New();
623 mPanGestureDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan );
626 void CreateActiveLayer()
630 mActiveLayer = Layer::New();
631 #ifdef DECORATOR_DEBUG
632 mActiveLayer.SetName ( "ActiveLayerActor" );
635 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER );
636 mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
637 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
639 // Add the active layer telling the controller it doesn't need clipping.
640 mController.AddDecoration( mActiveLayer, false );
643 mActiveLayer.RaiseToTop();
646 void SetSelectionHandleMarkerSize( HandleImpl& handle )
648 if( handle.markerActor )
650 handle.markerActor.SetSize( 0, handle.lineHeight );
654 void CreateGrabHandle()
656 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
657 if( !grabHandle.actor )
659 grabHandle.actor = ImageActor::New( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] );
660 grabHandle.actor.SetSortModifier( DECORATION_DEPTH_INDEX );
661 grabHandle.actor.SetAnchorPoint( AnchorPoint::TOP_CENTER );
662 // Area that Grab handle responds to, larger than actual handle so easier to move
663 #ifdef DECORATOR_DEBUG
664 grabHandle.actor.SetName( "GrabHandleActor" );
665 if ( Dali::Internal::gLogFilter->IsEnabledFor( Debug::Verbose ) )
667 grabHandle.grabArea = Toolkit::CreateSolidColorActor( Vector4(0.0f, 0.0f, 0.0f, 0.0f), true, Color::RED, 1 );
668 grabHandle.grabArea.SetName( "GrabArea" );
672 grabHandle.grabArea = Actor::New();
673 grabHandle.grabArea.SetName( "GrabArea" );
676 grabHandle.grabArea = Actor::New();
679 grabHandle.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
680 grabHandle.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
681 grabHandle.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
682 grabHandle.grabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
683 grabHandle.actor.Add( grabHandle.grabArea );
684 grabHandle.actor.SetColor( mHandleColor );
686 grabHandle.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnGrabHandleTouched );
687 mTapDetector.Attach( grabHandle.grabArea );
688 mPanGestureDetector.Attach( grabHandle.grabArea );
690 mActiveLayer.Add( grabHandle.actor );
693 if( !grabHandle.actor.GetParent() )
695 mActiveLayer.Add( grabHandle.actor );
699 void CreateHandleMarker( HandleImpl& handle, Image& image, HandleType handleType )
703 handle.markerActor = ImageActor::New( image );
704 handle.markerActor.SetColor( mHandleColor );
705 handle.actor.Add( handle.markerActor );
707 handle.markerActor.SetResizePolicy ( ResizePolicy::FIXED, Dimension::HEIGHT );
709 if( LEFT_SELECTION_HANDLE == handleType )
711 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_RIGHT );
712 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_RIGHT );
714 else if( RIGHT_SELECTION_HANDLE == handleType )
716 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_LEFT );
717 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
722 void CreateSelectionHandles()
724 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
727 primary.actor = ImageActor::New( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
728 #ifdef DECORATOR_DEBUG
729 primary.actor.SetName("SelectionHandleOne");
731 primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
732 primary.actor.SetSortModifier( DECORATION_DEPTH_INDEX );
733 primary.actor.SetColor( mHandleColor );
735 primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
736 #ifdef DECORATOR_DEBUG
737 primary.grabArea.SetName("SelectionHandleOneGrabArea");
739 primary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
740 primary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
741 primary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
742 primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
744 mTapDetector.Attach( primary.grabArea );
745 mPanGestureDetector.Attach( primary.grabArea );
746 primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched );
748 primary.actor.Add( primary.grabArea );
750 CreateHandleMarker( primary, mHandleImages[LEFT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], LEFT_SELECTION_HANDLE );
753 if( !primary.actor.GetParent() )
755 mActiveLayer.Add( primary.actor );
758 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
759 if( !secondary.actor )
761 secondary.actor = ImageActor::New( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
762 #ifdef DECORATOR_DEBUG
763 secondary.actor.SetName("SelectionHandleTwo");
765 secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text.
766 secondary.actor.SetSortModifier( DECORATION_DEPTH_INDEX );
767 secondary.actor.SetColor( mHandleColor );
769 secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
770 #ifdef DECORATOR_DEBUG
771 secondary.grabArea.SetName("SelectionHandleTwoGrabArea");
773 secondary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
774 secondary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
775 secondary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
776 secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
778 mTapDetector.Attach( secondary.grabArea );
779 mPanGestureDetector.Attach( secondary.grabArea );
780 secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched );
782 secondary.actor.Add( secondary.grabArea );
784 CreateHandleMarker( secondary, mHandleImages[RIGHT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], RIGHT_SELECTION_HANDLE );
787 if( !secondary.actor.GetParent() )
789 mActiveLayer.Add( secondary.actor );
793 void CalculateHandleWorldCoordinates( HandleImpl& handle, Vector2& position )
795 // Gets the world position of the active layer. The active layer is where the handles are added.
796 const Vector3 parentWorldPosition = mActiveLayer.GetCurrentWorldPosition();
798 // The grab handle position in world coords.
799 // The active layer's world position is the center of the active layer. The origin of the
800 // coord system of the handles is the top left of the active layer.
801 position.x = parentWorldPosition.x - 0.5f * mControlSize.width + handle.position.x;
802 position.y = parentWorldPosition.y - 0.5f * mControlSize.height + handle.position.y;
805 void SetGrabHandlePosition()
807 // Reference to the grab handle.
808 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
810 // Transforms the handle position into world coordinates.
811 // @note This is not the same value as grabHandle.actor.GetCurrentWorldPosition()
812 // as it's transforming the handle's position set by the text-controller and not
813 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
814 // retrieves the position of the center of the actor but the handle's position set
815 // by the text controller is not the center of the actor.
816 Vector2 grabHandleWorldPosition;
817 CalculateHandleWorldCoordinates( grabHandle, grabHandleWorldPosition );
819 // Check if the grab handle exceeds the boundaries of the decoration box.
820 // At the moment only the height is checked for the grab handle.
822 grabHandle.verticallyFlipped = ( grabHandle.verticallyFlippedPreferred &&
823 ( ( grabHandleWorldPosition.y - grabHandle.size.height ) > mBoundingBox.y ) ) ||
824 ( grabHandleWorldPosition.y + grabHandle.lineHeight + grabHandle.size.height > mBoundingBox.w );
826 // The grab handle 'y' position in local coords.
827 // If the grab handle exceeds the bottom of the decoration box,
828 // set the 'y' position to the top of the line.
829 // The SetGrabHandleImage() method will change the orientation.
830 const float yLocalPosition = grabHandle.verticallyFlipped ? grabHandle.position.y : grabHandle.position.y + grabHandle.lineHeight;
832 grabHandle.actor.SetPosition( grabHandle.position.x + floor( 0.5f * mCursorWidth ),
833 yLocalPosition ); // TODO : Fix for multiline.
836 void SetSelectionHandlePosition( HandleType type )
838 const bool isPrimaryHandle = LEFT_SELECTION_HANDLE == type;
840 // Reference to the selection handle.
841 HandleImpl& handle = mHandle[type];
843 // Transforms the handle position into world coordinates.
844 // @note This is not the same value as handle.actor.GetCurrentWorldPosition()
845 // as it's transforming the handle's position set by the text-controller and not
846 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
847 // retrieves the position of the center of the actor but the handle's position set
848 // by the text controller is not the center of the actor.
849 Vector2 handleWorldPosition;
850 CalculateHandleWorldCoordinates( handle, handleWorldPosition );
852 // Whether to flip the handle (horizontally).
853 bool flipHandle = isPrimaryHandle ? mFlipLeftSelectionHandleDirection : mFlipRightSelectionHandleDirection;
855 // Whether to flip the handles if they are crossed.
856 bool crossFlip = false;
857 if( mFlipSelectionHandlesOnCross || !mHandlePanning )
859 crossFlip = mHandleCurrentCrossed;
862 // Does not flip if both conditions are true (double flip)
863 flipHandle = flipHandle != ( crossFlip || mHandlePreviousCrossed );
865 // Will flip the handles vertically if the user prefers it.
866 bool verticallyFlippedPreferred = handle.verticallyFlippedPreferred;
868 if( crossFlip || mHandlePreviousCrossed )
870 if( isPrimaryHandle )
872 verticallyFlippedPreferred = mHandle[RIGHT_SELECTION_HANDLE].verticallyFlippedPreferred;
876 verticallyFlippedPreferred = mHandle[LEFT_SELECTION_HANDLE].verticallyFlippedPreferred;
880 // Check if the selection handle exceeds the boundaries of the decoration box.
881 const bool exceedsLeftEdge = ( isPrimaryHandle ? !flipHandle : flipHandle ) && ( handleWorldPosition.x - handle.size.width < mBoundingBox.x );
882 const bool exceedsRightEdge = ( isPrimaryHandle ? flipHandle : !flipHandle ) && ( handleWorldPosition.x + handle.size.width > mBoundingBox.z );
884 // Does not flip if both conditions are true (double flip)
885 flipHandle = flipHandle != ( exceedsLeftEdge || exceedsRightEdge );
889 if( !handle.horizontallyFlipped )
891 // Change the anchor point to flip the image.
892 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_LEFT : AnchorPoint::TOP_RIGHT );
894 handle.horizontallyFlipped = true;
899 if( handle.horizontallyFlipped )
901 // Reset the anchor point.
902 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_RIGHT : AnchorPoint::TOP_LEFT );
904 handle.horizontallyFlipped = false;
908 // Whether to flip the handle vertically.
909 handle.verticallyFlipped = ( verticallyFlippedPreferred &&
910 ( ( handleWorldPosition.y - handle.size.height ) > mBoundingBox.y ) ) ||
911 ( handleWorldPosition.y + handle.lineHeight + handle.size.height > mBoundingBox.w );
913 // The primary selection handle 'y' position in local coords.
914 // If the handle exceeds the bottom of the decoration box,
915 // set the 'y' position to the top of the line.
916 // The SetHandleImage() method will change the orientation.
917 const float yLocalPosition = handle.verticallyFlipped ? handle.position.y : handle.position.y + handle.lineHeight;
919 handle.actor.SetPosition( handle.position.x,
920 yLocalPosition ); // TODO : Fix for multiline.
923 void SetHandleImage( HandleType type )
925 HandleImpl& handle = mHandle[type];
927 HandleType markerType = HANDLE_TYPE_COUNT;
928 // If the selection handle is flipped it chooses the image of the other selection handle. Does nothing for the grab handle.
929 if( LEFT_SELECTION_HANDLE == type )
931 type = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE;
932 markerType = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE_MARKER : LEFT_SELECTION_HANDLE_MARKER;
934 else if( RIGHT_SELECTION_HANDLE == type )
936 type = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE : RIGHT_SELECTION_HANDLE;
937 markerType = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE_MARKER : RIGHT_SELECTION_HANDLE_MARKER;
940 // Chooses between the released or pressed image. It checks whether the pressed image exists.
943 const HandleImageType imageType = ( handle.pressed ? ( mHandleImages[type][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
945 handle.actor.SetImage( mHandleImages[type][imageType] );
948 if( HANDLE_TYPE_COUNT != markerType )
950 if( handle.markerActor )
952 const HandleImageType markerImageType = ( handle.pressed ? ( mHandleImages[markerType][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
953 handle.markerActor.SetImage( mHandleImages[markerType][markerImageType] );
957 // Whether to flip the handle vertically.
960 handle.actor.SetOrientation( handle.verticallyFlipped ? ANGLE_180 : ANGLE_0, Vector3::XAXIS );
964 void CreateHighlight()
966 if( !mHighlightActor )
968 mHighlightActor = Actor::New();
970 #ifdef DECORATOR_DEBUG
971 mHighlightActor.SetName( "HighlightActor" );
973 mHighlightActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
974 mHighlightActor.SetSize( 1.0f, 1.0f );
975 mHighlightActor.SetColor( mHighlightColor );
976 mHighlightActor.SetColorMode( USE_OWN_COLOR );
979 // Add the highlight box telling the controller it needs clipping.
980 mController.AddDecoration( mHighlightActor, true );
983 void UpdateHighlight()
985 if ( mHighlightActor )
987 if( !mHighlightQuadList.empty() )
989 Vector< Vector2 > vertices;
990 Vector< unsigned int> indices;
993 std::vector<QuadCoordinates>::iterator iter = mHighlightQuadList.begin();
994 std::vector<QuadCoordinates>::iterator endIter = mHighlightQuadList.end();
996 for( std::size_t v = 0; iter != endIter; ++iter,v+=4 )
998 QuadCoordinates& quad = *iter;
1001 vertex.x = quad.min.x;
1002 vertex.y = quad.min.y;
1003 vertices.PushBack( vertex );
1006 vertex.x = quad.max.x;
1007 vertex.y = quad.min.y;
1008 vertices.PushBack( vertex );
1010 // bottom-left (v+2)
1011 vertex.x = quad.min.x;
1012 vertex.y = quad.max.y;
1013 vertices.PushBack( vertex );
1015 // bottom-right (v+3)
1016 vertex.x = quad.max.x;
1017 vertex.y = quad.max.y;
1018 vertices.PushBack( vertex );
1020 // triangle A (3, 1, 0)
1021 indices.PushBack( v + 3 );
1022 indices.PushBack( v + 1 );
1023 indices.PushBack( v );
1025 // triangle B (0, 2, 3)
1026 indices.PushBack( v );
1027 indices.PushBack( v + 2 );
1028 indices.PushBack( v + 3 );
1033 mQuadVertices.SetSize( vertices.Size() );
1037 mQuadVertices = PropertyBuffer::New( mQuadVertexFormat, vertices.Size() );
1042 mQuadIndices.SetSize( indices.Size() );
1046 mQuadIndices = PropertyBuffer::New( mQuadIndexFormat, indices.Size() );
1049 mQuadVertices.SetData( &vertices[ 0 ] );
1050 mQuadIndices.SetData( &indices[ 0 ] );
1052 if( !mQuadGeometry )
1054 mQuadGeometry = Geometry::New();
1055 mQuadGeometry.AddVertexBuffer( mQuadVertices );
1057 mQuadGeometry.SetIndexBuffer( mQuadIndices );
1059 if( !mHighlightRenderer )
1061 mHighlightRenderer = Dali::Renderer::New( mQuadGeometry, mHighlightMaterial );
1062 mHighlightActor.AddRenderer( mHighlightRenderer );
1066 mHighlightActor.SetPosition( mHighlightPosition.x,
1067 mHighlightPosition.y );
1069 mHighlightQuadList.clear();
1071 if( mHighlightRenderer )
1073 mHighlightRenderer.SetDepthIndex( mTextDepth - 2u ); // text is rendered at mTextDepth and text's shadow at mTextDepth -1u.
1078 void OnTap( Actor actor, const TapGesture& tap )
1080 if( actor == mHandle[GRAB_HANDLE].actor )
1086 void DoPan( HandleImpl& handle, HandleType type, const PanGesture& gesture )
1088 if( Gesture::Started == gesture.state )
1090 handle.grabDisplacementX = handle.grabDisplacementY = 0;
1093 handle.grabDisplacementX += gesture.displacement.x;
1094 handle.grabDisplacementY += gesture.displacement.y;
1096 const float x = handle.position.x + handle.grabDisplacementX;
1097 const float y = handle.position.y + handle.lineHeight*0.5f + handle.grabDisplacementY;
1099 if( Gesture::Started == gesture.state ||
1100 Gesture::Continuing == gesture.state )
1103 mController.GetTargetSize( targetSize );
1105 if( x < mScrollThreshold )
1107 mScrollDirection = SCROLL_RIGHT;
1108 mHandleScrolling = type;
1111 else if( x > targetSize.width - mScrollThreshold )
1113 mScrollDirection = SCROLL_LEFT;
1114 mHandleScrolling = type;
1119 mHandleScrolling = HANDLE_TYPE_COUNT;
1121 mController.DecorationEvent( type, HANDLE_PRESSED, x, y );
1124 mHandlePanning = true;
1126 else if( Gesture::Finished == gesture.state ||
1127 Gesture::Cancelled == gesture.state )
1130 ( mScrollTimer.IsRunning() || mNotifyEndOfScroll ) )
1132 mNotifyEndOfScroll = false;
1133 mHandleScrolling = HANDLE_TYPE_COUNT;
1135 mController.DecorationEvent( type, HANDLE_STOP_SCROLLING, x, y );
1139 mController.DecorationEvent( type, HANDLE_RELEASED, x, y );
1142 handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_RELEASED] );
1143 handle.pressed = false;
1145 mHandlePanning = false;
1149 void OnPan( Actor actor, const PanGesture& gesture )
1151 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1152 HandleImpl& primarySelectionHandle = mHandle[LEFT_SELECTION_HANDLE];
1153 HandleImpl& secondarySelectionHandle = mHandle[RIGHT_SELECTION_HANDLE];
1155 if( actor == grabHandle.grabArea )
1157 DoPan( grabHandle, GRAB_HANDLE, gesture );
1159 else if( actor == primarySelectionHandle.grabArea )
1161 DoPan( primarySelectionHandle, LEFT_SELECTION_HANDLE, gesture );
1163 else if( actor == secondarySelectionHandle.grabArea )
1165 DoPan( secondarySelectionHandle, RIGHT_SELECTION_HANDLE, gesture );
1169 bool OnGrabHandleTouched( Actor actor, const TouchEvent& event )
1171 // Switch between pressed/release grab-handle images
1172 if( event.GetPointCount() > 0 &&
1173 mHandle[GRAB_HANDLE].actor )
1175 const TouchPoint& point = event.GetPoint(0);
1177 if( TouchPoint::Down == point.state )
1179 mHandle[GRAB_HANDLE].pressed = true;
1181 else if( ( TouchPoint::Up == point.state ) ||
1182 ( TouchPoint::Interrupted == point.state ) )
1184 mHandle[GRAB_HANDLE].pressed = false;
1187 SetHandleImage( GRAB_HANDLE );
1190 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1194 bool OnHandleOneTouched( Actor actor, const TouchEvent& event )
1196 // Switch between pressed/release selection handle images
1197 if( event.GetPointCount() > 0 &&
1198 mHandle[LEFT_SELECTION_HANDLE].actor )
1200 const TouchPoint& point = event.GetPoint(0);
1202 if( TouchPoint::Down == point.state )
1204 mHandle[LEFT_SELECTION_HANDLE].pressed = true;
1206 else if( ( TouchPoint::Up == point.state ) ||
1207 ( TouchPoint::Interrupted == point.state ) )
1209 mHandle[LEFT_SELECTION_HANDLE].pressed = false;
1210 mHandlePreviousCrossed = mHandleCurrentCrossed;
1211 mHandlePanning = false;
1214 SetHandleImage( LEFT_SELECTION_HANDLE );
1217 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1221 bool OnHandleTwoTouched( Actor actor, const TouchEvent& event )
1223 // Switch between pressed/release selection handle images
1224 if( event.GetPointCount() > 0 &&
1225 mHandle[RIGHT_SELECTION_HANDLE].actor )
1227 const TouchPoint& point = event.GetPoint(0);
1229 if( TouchPoint::Down == point.state )
1231 mHandle[RIGHT_SELECTION_HANDLE].pressed = true;
1233 else if( ( TouchPoint::Up == point.state ) ||
1234 ( TouchPoint::Interrupted == point.state ) )
1236 mHandle[RIGHT_SELECTION_HANDLE].pressed = false;
1237 mHandlePreviousCrossed = mHandleCurrentCrossed;
1238 mHandlePanning = false;
1241 SetHandleImage( RIGHT_SELECTION_HANDLE );
1244 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1248 void HandleResetPosition( PropertyNotification& source )
1250 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1252 if( grabHandle.active )
1254 // Sets the grab handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1255 SetGrabHandlePosition();
1257 // Sets the grab handle image according if it's pressed, flipped, etc.
1258 SetHandleImage( GRAB_HANDLE );
1262 // Sets the primary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1263 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
1265 // Sets the primary handle image according if it's pressed, flipped, etc.
1266 SetHandleImage( LEFT_SELECTION_HANDLE );
1268 // Sets the secondary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1269 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
1271 // Sets the secondary handle image according if it's pressed, flipped, etc.
1272 SetHandleImage( RIGHT_SELECTION_HANDLE );
1276 void SetupActiveLayerPropertyNotifications()
1283 // Vertical notifications.
1285 // Disconnect any previous connected callback.
1286 if( mVerticalLessThanNotification )
1288 mVerticalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1289 mActiveLayer.RemovePropertyNotification( mVerticalLessThanNotification );
1292 if( mVerticalGreaterThanNotification )
1294 mVerticalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1295 mActiveLayer.RemovePropertyNotification( mVerticalGreaterThanNotification );
1298 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1299 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1300 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1302 if( grabHandle.active )
1304 if( grabHandle.verticallyFlipped )
1306 // The grab handle is vertically flipped. Never is going to exceed the bottom edje of the display.
1307 mVerticalGreaterThanNotification.Reset();
1309 // The vertical distance from the center of the active layer to the top edje of the display.
1310 const float topHeight = 0.5f * mControlSize.height - grabHandle.position.y + grabHandle.size.height;
1312 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1313 LessThanCondition( mBoundingBox.y + topHeight ) );
1315 // Notifies the change from false to true and from true to false.
1316 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1318 // Connects the signals with the callbacks.
1319 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1323 // The grab handle is not vertically flipped. Never is going to exceed the top edje of the display.
1324 mVerticalLessThanNotification.Reset();
1326 // The vertical distance from the center of the active layer to the bottom edje of the display.
1327 const float bottomHeight = -0.5f * mControlSize.height + grabHandle.position.y + grabHandle.lineHeight + grabHandle.size.height;
1329 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1330 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1332 // Notifies the change from false to true and from true to false.
1333 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1335 // Connects the signals with the callbacks.
1336 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1339 else // The selection handles are active
1341 if( primaryHandle.verticallyFlipped && secondaryHandle.verticallyFlipped )
1343 // Both selection handles are vertically flipped. Never are going to exceed the bottom edje of the display.
1344 mVerticalGreaterThanNotification.Reset();
1346 // The vertical distance from the center of the active layer to the top edje of the display.
1347 const float topHeight = 0.5f * mControlSize.height + std::max( -primaryHandle.position.y + primaryHandle.size.height, -secondaryHandle.position.y + secondaryHandle.size.height );
1349 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1350 LessThanCondition( mBoundingBox.y + topHeight ) );
1352 // Notifies the change from false to true and from true to false.
1353 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1355 // Connects the signals with the callbacks.
1356 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1358 else if( !primaryHandle.verticallyFlipped && !secondaryHandle.verticallyFlipped )
1360 // Both selection handles aren't vertically flipped. Never are going to exceed the top edje of the display.
1361 mVerticalLessThanNotification.Reset();
1363 // The vertical distance from the center of the active layer to the bottom edje of the display.
1364 const float bottomHeight = -0.5f * mControlSize.height + std::max( primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height,
1365 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height );
1367 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1368 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1370 // Notifies the change from false to true and from true to false.
1371 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1373 // Connects the signals with the callbacks.
1374 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1378 // Only one of the selection handles is vertically flipped. Both vertical notifications are needed.
1380 // The vertical distance from the center of the active layer to the top edje of the display.
1381 const float topHeight = 0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1382 -primaryHandle.position.y + primaryHandle.size.height :
1383 -secondaryHandle.position.y + secondaryHandle.size.height );
1385 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1386 LessThanCondition( mBoundingBox.y + topHeight ) );
1388 // Notifies the change from false to true and from true to false.
1389 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1391 // Connects the signals with the callbacks.
1392 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1394 // The vertical distance from the center of the active layer to the bottom edje of the display.
1395 const float bottomHeight = -0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1396 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height :
1397 primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height );
1399 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1400 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1402 // Notifies the change from false to true and from true to false.
1403 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1405 // Connects the signals with the callbacks.
1406 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1410 // Horizontal notifications.
1412 // Disconnect any previous connected callback.
1413 if( mHorizontalLessThanNotification )
1415 mHorizontalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1416 mActiveLayer.RemovePropertyNotification( mHorizontalLessThanNotification );
1419 if( mHorizontalGreaterThanNotification )
1421 mHorizontalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1422 mActiveLayer.RemovePropertyNotification( mHorizontalGreaterThanNotification );
1425 if( primaryHandle.active || secondaryHandle.active )
1427 // The horizontal distance from the center of the active layer to the left edje of the display.
1428 const float leftWidth = 0.5f * mControlSize.width + std::max( -primaryHandle.position.x + primaryHandle.size.width,
1429 -secondaryHandle.position.x + secondaryHandle.size.width );
1431 mHorizontalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1432 LessThanCondition( mBoundingBox.x + leftWidth ) );
1434 // Notifies the change from false to true and from true to false.
1435 mHorizontalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1437 // Connects the signals with the callbacks.
1438 mHorizontalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1440 // The horizontal distance from the center of the active layer to the right edje of the display.
1441 const float rightWidth = -0.5f * mControlSize.width + std::max( primaryHandle.position.x + primaryHandle.size.width,
1442 secondaryHandle.position.x + secondaryHandle.size.width );
1444 mHorizontalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1445 GreaterThanCondition( mBoundingBox.z - rightWidth ) );
1447 // Notifies the change from false to true and from true to false.
1448 mHorizontalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1450 // Connects the signals with the callbacks.
1451 mHorizontalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1457 float AlternatePopUpPositionRelativeToCursor()
1459 float alternativePosition = 0.0f;
1461 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1463 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1464 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1465 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1466 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
1468 if( primaryHandle.active || secondaryHandle.active )
1470 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
1471 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
1475 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + grabHandle.size.height + cursor.position.y;
1478 return alternativePosition;
1481 void PopUpLeavesVerticalBoundary( PropertyNotification& source )
1483 float alternativeYPosition = 0.0f;
1484 // todo use AlternatePopUpPositionRelativeToSelectionHandles() if text is highlighted
1485 // if can't be positioned above, then position below row.
1486 alternativeYPosition = AlternatePopUpPositionRelativeToCursor();
1488 mCopyPastePopup.actor.SetY( alternativeYPosition );
1491 void SetUpPopupPositionNotifications()
1493 // Note Property notifications ignore any set anchor point so conditions must allow for this. Default is Top Left.
1495 // Exceeding vertical boundary
1497 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1499 PropertyNotification verticalExceedNotification = mCopyPastePopup.actor.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1500 OutsideCondition( mBoundingBox.y + popupHeight * 0.5f,
1501 mBoundingBox.w - popupHeight * 0.5f ) );
1503 verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary );
1506 void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, const Vector3& popupDistanceFromAnchorPoint, Actor parent, const Vector4& boundingRectangleWorld )
1508 DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() )
1510 // Parent must already by added to Stage for these Get calls to work
1511 const Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize() * parent.GetCurrentAnchorPoint();
1512 const Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition; // Parent World position plus popup local position gives World Position
1514 // Calculate distance to move popup (in local space) so fits within the boundary
1515 float xOffSetToKeepWithinBounds = 0.0f;
1516 if( popupWorldPosition.x - popupDistanceFromAnchorPoint.x < boundingRectangleWorld.x )
1518 xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x );
1520 else if( popupWorldPosition.x + popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z )
1522 xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x + popupDistanceFromAnchorPoint.x );
1525 // Ensure initial display of Popup is in alternative position if can not fit above. As Property notification will be a frame behind.
1526 if( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y )
1528 requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor();
1531 requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds;
1533 // Prevent pixel mis-alignment by rounding down.
1534 requiredPopupPosition.x = floor( requiredPopupPosition.x );
1535 requiredPopupPosition.y = floor( requiredPopupPosition.y );
1538 void SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1540 HandleImpl& handle = mHandle[handleType];
1541 handle.size = Size( image.GetWidth(), image.GetHeight() );
1543 mHandleImages[handleType][handleImageType] = image;
1546 void SetScrollThreshold( float threshold )
1548 mScrollThreshold = threshold;
1551 float GetScrollThreshold() const
1553 return mScrollThreshold;
1556 void SetScrollSpeed( float speed )
1558 mScrollSpeed = speed;
1559 mScrollDistance = speed * SCROLL_TICK_INTERVAL * TO_SECONDS;
1562 float GetScrollSpeed() const
1564 return mScrollSpeed;
1567 void NotifyEndOfScroll()
1573 mNotifyEndOfScroll = true;
1578 * Creates and starts a timer to scroll the text when handles are close to the edges of the text.
1580 * It only starts the timer if it's already created.
1582 void StartScrollTimer()
1586 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1587 mScrollTimer.TickSignal().Connect( this, &Decorator::Impl::OnScrollTimerTick );
1590 if( !mScrollTimer.IsRunning() )
1592 mScrollTimer.Start();
1597 * Stops the timer used to scroll the text.
1599 void StopScrollTimer()
1603 mScrollTimer.Stop();
1608 * Callback called by the timer used to scroll the text.
1610 * It calculates and sets a new scroll position.
1612 bool OnScrollTimerTick()
1614 if( HANDLE_TYPE_COUNT != mHandleScrolling )
1616 mController.DecorationEvent( mHandleScrolling,
1618 mScrollDirection == SCROLL_RIGHT ? mScrollDistance : -mScrollDistance,
1625 ControllerInterface& mController;
1627 TapGestureDetector mTapDetector;
1628 PanGestureDetector mPanGestureDetector;
1629 Timer mCursorBlinkTimer; ///< Timer to signal cursor to blink
1630 Timer mScrollTimer; ///< Timer used to scroll the text when the grab handle is moved close to the edges.
1632 Layer mActiveLayer; ///< Layer for active handles and alike that ensures they are above all else.
1633 PropertyNotification mVerticalLessThanNotification; ///< Notifies when the 'y' coord of the active layer is less than a given value.
1634 PropertyNotification mVerticalGreaterThanNotification; ///< Notifies when the 'y' coord of the active layer is grater than a given value.
1635 PropertyNotification mHorizontalLessThanNotification; ///< Notifies when the 'x' coord of the active layer is less than a given value.
1636 PropertyNotification mHorizontalGreaterThanNotification; ///< Notifies when the 'x' coord of the active layer is grater than a given value.
1637 ImageActor mPrimaryCursor;
1638 ImageActor mSecondaryCursor;
1640 Actor mHighlightActor; ///< Actor to display highlight
1641 Renderer mHighlightRenderer;
1642 Material mHighlightMaterial; ///< Material used for highlight
1643 Property::Map mQuadVertexFormat;
1644 Property::Map mQuadIndexFormat;
1645 PopupImpl mCopyPastePopup;
1646 TextSelectionPopup::Buttons mEnabledPopupButtons; /// Bit mask of currently enabled Popup buttons
1647 TextSelectionPopupCallbackInterface& mTextSelectionPopupCallbackInterface;
1649 Image mHandleImages[HANDLE_TYPE_COUNT][HANDLE_IMAGE_TYPE_COUNT];
1650 Vector4 mHandleColor;
1652 CursorImpl mCursor[CURSOR_COUNT];
1653 HandleImpl mHandle[HANDLE_TYPE_COUNT];
1655 PropertyBuffer mQuadVertices;
1656 PropertyBuffer mQuadIndices;
1657 Geometry mQuadGeometry;
1658 QuadContainer mHighlightQuadList; ///< Sub-selections that combine to create the complete selection highlight
1660 Vector4 mBoundingBox; ///< The bounding box in world coords.
1661 Vector4 mHighlightColor; ///< Color of the highlight
1662 Vector2 mHighlightPosition; ///< The position of the highlight actor.
1663 Vector2 mControlSize; ///< The control's size. Set by the Relayout.
1665 unsigned int mActiveCursor;
1666 unsigned int mCursorBlinkInterval;
1667 float mCursorBlinkDuration;
1668 float mCursorWidth; ///< The width of the cursors in pixels.
1669 HandleType mHandleScrolling; ///< The handle which is scrolling.
1670 ScrollDirection mScrollDirection; ///< The direction of the scroll.
1671 float mScrollThreshold; ///< Defines a square area inside the control, close to the edge. A cursor entering this area will trigger scroll events.
1672 float mScrollSpeed; ///< The scroll speed in pixels per second.
1673 float mScrollDistance; ///< Distance the text scrolls during a scroll interval.
1674 int mTextDepth; ///< The depth used to render the text.
1676 bool mActiveCopyPastePopup : 1;
1677 bool mPopupSetNewPosition : 1;
1678 bool mCursorBlinkStatus : 1; ///< Flag to switch between blink on and blink off.
1679 bool mDelayCursorBlink : 1; ///< Used to avoid cursor blinking when entering text.
1680 bool mPrimaryCursorVisible : 1; ///< Whether the primary cursor is visible.
1681 bool mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible.
1682 bool mFlipSelectionHandlesOnCross : 1; ///< Whether to flip the selection handles as soon as they cross.
1683 bool mFlipLeftSelectionHandleDirection : 1; ///< Whether to flip the left selection handle image because of the character's direction.
1684 bool mFlipRightSelectionHandleDirection : 1; ///< Whether to flip the right selection handle image because of the character's direction.
1685 bool mHandlePanning : 1; ///< Whether any of the handles is moving.
1686 bool mHandleCurrentCrossed : 1; ///< Whether the handles are crossed.
1687 bool mHandlePreviousCrossed : 1; ///< Whether the handles where crossed at the last handle touch up.
1688 bool mNotifyEndOfScroll : 1; ///< Whether to notify the end of the scroll.
1691 DecoratorPtr Decorator::New( ControllerInterface& controller,
1692 TextSelectionPopupCallbackInterface& callbackInterface )
1694 return DecoratorPtr( new Decorator( controller,
1695 callbackInterface ) );
1698 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
1700 LocalToWorldCoordinatesBoundingBox( boundingBox, mImpl->mBoundingBox );
1703 void Decorator::GetBoundingBox( Rect<int>& boundingBox ) const
1705 WorldToLocalCoordinatesBoundingBox( mImpl->mBoundingBox, boundingBox );
1708 void Decorator::Relayout( const Vector2& size )
1710 mImpl->Relayout( size );
1713 void Decorator::UpdatePositions( const Vector2& scrollOffset )
1715 mImpl->UpdatePositions( scrollOffset );
1720 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
1722 mImpl->mActiveCursor = activeCursor;
1725 unsigned int Decorator::GetActiveCursor() const
1727 return mImpl->mActiveCursor;
1730 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
1732 mImpl->mCursor[cursor].position.x = x;
1733 mImpl->mCursor[cursor].position.y = y;
1734 mImpl->mCursor[cursor].cursorHeight = cursorHeight;
1735 mImpl->mCursor[cursor].lineHeight = lineHeight;
1738 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
1740 x = mImpl->mCursor[cursor].position.x;
1741 y = mImpl->mCursor[cursor].position.y;
1742 cursorHeight = mImpl->mCursor[cursor].cursorHeight;
1743 lineHeight = mImpl->mCursor[cursor].lineHeight;
1746 const Vector2& Decorator::GetPosition( Cursor cursor ) const
1748 return mImpl->mCursor[cursor].position;
1751 void Decorator::SetCursorColor( Cursor cursor, const Dali::Vector4& color )
1753 mImpl->mCursor[cursor].color = color;
1756 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
1758 return mImpl->mCursor[cursor].color;
1761 void Decorator::StartCursorBlink()
1763 if ( !mImpl->mCursorBlinkTimer )
1765 mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
1766 mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
1769 if ( !mImpl->mCursorBlinkTimer.IsRunning() )
1771 mImpl->mCursorBlinkTimer.Start();
1775 void Decorator::StopCursorBlink()
1777 if ( mImpl->mCursorBlinkTimer )
1779 mImpl->mCursorBlinkTimer.Stop();
1782 mImpl->mCursorBlinkStatus = true; // Keep cursor permanently shown
1785 void Decorator::DelayCursorBlink()
1787 mImpl->mCursorBlinkStatus = true; // Show cursor for a bit longer
1788 mImpl->mDelayCursorBlink = true;
1791 void Decorator::SetCursorBlinkInterval( float seconds )
1793 mImpl->mCursorBlinkInterval = static_cast<unsigned int>( seconds * TO_MILLISECONDS ); // Convert to milliseconds
1796 float Decorator::GetCursorBlinkInterval() const
1798 return static_cast<float>( mImpl->mCursorBlinkInterval ) * TO_SECONDS;
1801 void Decorator::SetCursorBlinkDuration( float seconds )
1803 mImpl->mCursorBlinkDuration = seconds;
1806 float Decorator::GetCursorBlinkDuration() const
1808 return mImpl->mCursorBlinkDuration;
1811 void Decorator::SetCursorWidth( int width )
1813 mImpl->mCursorWidth = static_cast<float>( width );
1816 int Decorator::GetCursorWidth() const
1818 return static_cast<int>( mImpl->mCursorWidth );
1823 void Decorator::SetHandleActive( HandleType handleType, bool active )
1825 mImpl->mHandle[handleType].active = active;
1829 if( ( LEFT_SELECTION_HANDLE == handleType ) || ( RIGHT_SELECTION_HANDLE == handleType ) )
1831 mImpl->mHandlePreviousCrossed = false;
1834 // TODO: this is a work-around.
1835 // The problem is the handle actor does not receive the touch event with the Interrupt
1836 // state when the power button is pressed and the application goes to background.
1837 mImpl->mHandle[handleType].pressed = false;
1838 Image imageReleased = mImpl->mHandleImages[handleType][HANDLE_IMAGE_RELEASED];
1839 ImageActor imageActor = mImpl->mHandle[handleType].actor;
1840 if( imageReleased && imageActor )
1842 imageActor.SetImage( imageReleased );
1848 bool Decorator::IsHandleActive( HandleType handleType ) const
1850 return mImpl->mHandle[handleType].active ;
1853 void Decorator::SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1855 mImpl->SetHandleImage( handleType, handleImageType, image );
1858 Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType handleImageType ) const
1860 return mImpl->mHandleImages[handleType][handleImageType];
1863 void Decorator::SetHandleColor( const Vector4& color )
1865 mImpl->mHandleColor = color;
1868 const Vector4& Decorator::GetHandleColor() const
1870 return mImpl->mHandleColor;
1873 void Decorator::SetPosition( HandleType handleType, float x, float y, float height )
1875 // Adjust grab handle displacement
1876 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1878 handle.grabDisplacementX -= x - handle.position.x;
1879 handle.grabDisplacementY -= y - handle.position.y;
1881 handle.position.x = x;
1882 handle.position.y = y;
1883 handle.lineHeight = height;
1886 void Decorator::GetPosition( HandleType handleType, float& x, float& y, float& height ) const
1888 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1890 x = handle.position.x;
1891 y = handle.position.y;
1892 height = handle.lineHeight;
1895 const Vector2& Decorator::GetPosition( HandleType handleType ) const
1897 return mImpl->mHandle[handleType].position;
1900 void Decorator::FlipHandleVertically( HandleType handleType, bool flip )
1902 mImpl->mHandle[handleType].verticallyFlippedPreferred = flip;
1905 bool Decorator::IsHandleVerticallyFlipped( HandleType handleType ) const
1907 return mImpl->mHandle[handleType].verticallyFlippedPreferred;
1910 void Decorator::FlipSelectionHandlesOnCrossEnabled( bool enable )
1912 mImpl->mFlipSelectionHandlesOnCross = enable;
1915 void Decorator::SetSelectionHandleFlipState( bool indicesSwapped, bool left, bool right )
1917 mImpl->mHandleCurrentCrossed = indicesSwapped;
1918 mImpl->mFlipLeftSelectionHandleDirection = left;
1919 mImpl->mFlipRightSelectionHandleDirection = right;
1922 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1924 mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1927 void Decorator::ClearHighlights()
1929 mImpl->mHighlightQuadList.clear();
1930 mImpl->mHighlightPosition = Vector2::ZERO;
1933 void Decorator::SetHighlightColor( const Vector4& color )
1935 mImpl->mHighlightColor = color;
1938 const Vector4& Decorator::GetHighlightColor() const
1940 return mImpl->mHighlightColor;
1943 void Decorator::SetTextDepth( int textDepth )
1945 mImpl->mTextDepth = textDepth;
1948 void Decorator::SetPopupActive( bool active )
1950 mImpl->mActiveCopyPastePopup = active;
1953 bool Decorator::IsPopupActive() const
1955 return mImpl->mActiveCopyPastePopup ;
1958 void Decorator::SetEnabledPopupButtons( TextSelectionPopup::Buttons& enabledButtonsBitMask )
1960 mImpl->mEnabledPopupButtons = enabledButtonsBitMask;
1962 if ( !mImpl->mCopyPastePopup.actor )
1964 mImpl->mCopyPastePopup.actor = TextSelectionPopup::New( &mImpl->mTextSelectionPopupCallbackInterface );
1965 #ifdef DECORATOR_DEBUG
1966 mImpl->mCopyPastePopup.actor.SetName("mCopyPastePopup");
1968 mImpl->mCopyPastePopup.actor.SetAnchorPoint( AnchorPoint::CENTER );
1969 mImpl->mCopyPastePopup.actor.OnRelayoutSignal().Connect( mImpl, &Decorator::Impl::PopupRelayoutComplete ); // Position popup after size negotiation
1972 mImpl->mCopyPastePopup.actor.EnableButtons( mImpl->mEnabledPopupButtons );
1975 TextSelectionPopup::Buttons& Decorator::GetEnabledPopupButtons()
1977 return mImpl->mEnabledPopupButtons;
1982 void Decorator::SetScrollThreshold( float threshold )
1984 mImpl->SetScrollThreshold( threshold );
1987 float Decorator::GetScrollThreshold() const
1989 return mImpl->GetScrollThreshold();
1992 void Decorator::SetScrollSpeed( float speed )
1994 mImpl->SetScrollSpeed( speed );
1997 float Decorator::GetScrollSpeed() const
1999 return mImpl->GetScrollSpeed();
2002 void Decorator::NotifyEndOfScroll()
2004 mImpl->NotifyEndOfScroll();
2007 Decorator::~Decorator()
2012 Decorator::Decorator( ControllerInterface& controller,
2013 TextSelectionPopupCallbackInterface& callbackInterface )
2016 mImpl = new Decorator::Impl( controller, callbackInterface );
2021 } // namespace Toolkit