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/default-controls/solid-color-actor.h>
37 #include <dali-toolkit/public-api/controls/image-view/image-view.h>
38 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
39 #include <dali-toolkit/internal/controls/image-view/image-view-impl.h>
42 #define DECORATOR_DEBUG
46 #define MAKE_SHADER(A)#A
50 const char* VERTEX_SHADER = MAKE_SHADER(
51 attribute mediump vec2 aPosition;
52 uniform mediump mat4 uMvpMatrix;
53 uniform mediump vec3 uSize;
57 mediump vec4 position = vec4( aPosition, 0.0, 1.0 );
58 position.xyz *= uSize;
59 gl_Position = uMvpMatrix * position;
63 const char* FRAGMENT_SHADER = MAKE_SHADER(
64 uniform lowp vec4 uColor;
68 gl_FragColor = uColor;
79 #ifdef DECORATOR_DEBUG
80 Integration::Log::Filter* gLogFilter( Integration::Log::Filter::New(Debug::NoLogging, false, "LOG_TEXT_DECORATOR") );
90 const Dali::Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.25f, 1.5f, 1.0f );
91 const Dali::Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.25f, 1.5f, 1.0f );
93 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.
95 const Dali::Vector4 HANDLE_COLOR( 0.0f, (183.0f / 255.0f), (229.0f / 255.0f), 1.0f );
97 const unsigned int CURSOR_BLINK_INTERVAL = 500u; // Cursor blink interval
98 const float TO_MILLISECONDS = 1000.f;
99 const float TO_SECONDS = 1.f / TO_MILLISECONDS;
101 const unsigned int SCROLL_TICK_INTERVAL = 50u;
103 const float SCROLL_THRESHOLD = 10.f;
104 const float SCROLL_SPEED = 300.f;
105 const float SCROLL_DISTANCE = SCROLL_SPEED * SCROLL_TICK_INTERVAL * TO_SECONDS;
107 const float CURSOR_WIDTH = 1.f;
110 * structure to hold coordinates of each quad, which will make up the mesh.
112 struct QuadCoordinates
115 * Default constructor
123 * @param[in] x1 left co-ordinate
124 * @param[in] y1 top co-ordinate
125 * @param[in] x2 right co-ordinate
126 * @param[in] y2 bottom co-ordinate
128 QuadCoordinates(float x1, float y1, float x2, float y2)
134 Dali::Vector2 min; ///< top-left (minimum) position of quad
135 Dali::Vector2 max; ///< bottom-right (maximum) position of quad
138 typedef std::vector<QuadCoordinates> QuadContainer;
141 * @brief Takes a bounding rectangle in the local coordinates of an actor and returns the world coordinates Bounding Box.
142 * @param[in] boundingRectangle local bounding
143 * @param[out] Vector4 World coordinate bounding Box.
145 void LocalToWorldCoordinatesBoundingBox( const Dali::Rect<int>& boundingRectangle, Dali::Vector4& boundingBox )
147 // Convert to world coordinates and store as a Vector4 to be compatible with Property Notifications.
148 Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
150 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
151 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
153 boundingBox = Dali::Vector4( originX,
155 originX + boundingRectangle.width,
156 originY + boundingRectangle.height );
159 void WorldToLocalCoordinatesBoundingBox( const Dali::Vector4& boundingBox, Dali::Rect<int>& boundingRectangle )
161 // Convert to local coordinates and store as a Dali::Rect.
162 Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
164 boundingRectangle.x = boundingBox.x + 0.5f * stageSize.width;
165 boundingRectangle.y = boundingBox.y + 0.5f * stageSize.height;
166 boundingRectangle.width = boundingBox.z - boundingBox.x;
167 boundingRectangle.height = boundingBox.w - boundingBox.y;
170 } // end of namespace
181 struct Decorator::Impl : public ConnectionTracker
195 : color( Dali::Color::BLACK ),
197 cursorHeight( 0.0f ),
214 grabDisplacementX( 0.f ),
215 grabDisplacementY( 0.f ),
219 verticallyFlippedPreferred( false ),
220 horizontallyFlipped( false ),
221 verticallyFlipped( false )
227 ImageView markerActor;
231 float lineHeight; ///< Not the handle height
232 float grabDisplacementX;
233 float grabDisplacementY;
237 bool verticallyFlippedPreferred : 1; ///< Whether the handle is preferred to be vertically flipped.
238 bool horizontallyFlipped : 1; ///< Whether the handle has been horizontally flipped.
239 bool verticallyFlipped : 1; ///< Whether the handle has been vertically flipped.
249 TextSelectionPopup actor;
253 Impl( ControllerInterface& controller,
254 TextSelectionPopupCallbackInterface& callbackInterface )
255 : mController( controller ),
256 mEnabledPopupButtons( TextSelectionPopup::NONE ),
257 mTextSelectionPopupCallbackInterface( callbackInterface ),
258 mHandleColor( HANDLE_COLOR ),
260 mHighlightColor( LIGHT_BLUE ),
261 mHighlightPosition( Vector2::ZERO ),
262 mActiveCursor( ACTIVE_CURSOR_NONE ),
263 mCursorBlinkInterval( CURSOR_BLINK_INTERVAL ),
264 mCursorBlinkDuration( 0.0f ),
265 mCursorWidth( CURSOR_WIDTH ),
266 mHandleScrolling( HANDLE_TYPE_COUNT ),
267 mScrollDirection( SCROLL_NONE ),
268 mScrollThreshold( SCROLL_THRESHOLD ),
269 mScrollSpeed( SCROLL_SPEED ),
270 mScrollDistance( SCROLL_DISTANCE ),
272 mActiveCopyPastePopup( false ),
273 mPopupSetNewPosition( true ),
274 mCursorBlinkStatus( true ),
275 mDelayCursorBlink( false ),
276 mPrimaryCursorVisible( false ),
277 mSecondaryCursorVisible( false ),
278 mFlipSelectionHandlesOnCross( false ),
279 mFlipLeftSelectionHandleDirection( false ),
280 mFlipRightSelectionHandleDirection( false ),
281 mHandlePanning( false ),
282 mHandleCurrentCrossed( false ),
283 mHandlePreviousCrossed( false ),
284 mNotifyEndOfScroll( false )
286 mQuadVertexFormat[ "aPosition" ] = Property::VECTOR2;
287 mQuadIndexFormat[ "indices" ] = Property::INTEGER;
288 mHighlightMaterial = Material::New( Shader::New( VERTEX_SHADER, FRAGMENT_SHADER ) );
294 * Relayout of the decorations owned by the decorator.
295 * @param[in] size The Size of the UI control the decorator is adding it's decorations to.
297 void Relayout( const Vector2& size )
301 // TODO - Remove this if nothing is active
304 // Show or hide the cursors
309 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
310 mPrimaryCursorVisible = ( cursor.position.x + mCursorWidth <= mControlSize.width ) && ( cursor.position.x >= 0.f );
311 if( mPrimaryCursorVisible )
313 mPrimaryCursor.SetPosition( cursor.position.x,
315 mPrimaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) );
317 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
319 if( mSecondaryCursor )
321 const CursorImpl& cursor = mCursor[SECONDARY_CURSOR];
322 mSecondaryCursorVisible = ( cursor.position.x + mCursorWidth <= mControlSize.width ) && ( cursor.position.x >= 0.f );
323 if( mSecondaryCursorVisible )
325 mSecondaryCursor.SetPosition( cursor.position.x,
327 mSecondaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) );
329 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
332 // Show or hide the grab handle
333 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
334 bool newGrabHandlePosition = false;
335 if( grabHandle.active )
337 const bool isVisible = ( grabHandle.position.x + floor( 0.5f * mCursorWidth ) <= mControlSize.width ) && ( grabHandle.position.x >= 0.f );
343 // Sets the grab handle position and calculate if it needs to be vertically flipped if it exceeds the boundary box.
344 SetGrabHandlePosition();
346 // Sets the grab handle image according if it's pressed, flipped, etc.
347 SetHandleImage( GRAB_HANDLE );
349 newGrabHandlePosition = true;
352 if( grabHandle.actor )
354 grabHandle.actor.SetVisible( isVisible );
357 else if( grabHandle.actor )
359 grabHandle.actor.Unparent();
362 // Show or hide the selection handles/highlight
363 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
364 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
365 bool newPrimaryHandlePosition = false;
366 bool newSecondaryHandlePosition = false;
367 if( primary.active || secondary.active )
369 const bool isPrimaryVisible = ( primary.position.x <= mControlSize.width ) && ( primary.position.x >= 0.f );
370 const bool isSecondaryVisible = ( secondary.position.x <= mControlSize.width ) && ( secondary.position.x >= 0.f );
372 if( isPrimaryVisible || isSecondaryVisible )
374 CreateSelectionHandles();
376 if( isPrimaryVisible )
378 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
380 // Sets the primary handle image according if it's pressed, flipped, etc.
381 SetHandleImage( LEFT_SELECTION_HANDLE );
383 SetSelectionHandleMarkerSize( primary );
385 newPrimaryHandlePosition = true;
388 if( isSecondaryVisible )
390 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
392 // Sets the secondary handle image according if it's pressed, flipped, etc.
393 SetHandleImage( RIGHT_SELECTION_HANDLE );
395 SetSelectionHandleMarkerSize( secondary );
397 newSecondaryHandlePosition = true;
403 primary.actor.SetVisible( isPrimaryVisible );
405 if( secondary.actor )
407 secondary.actor.SetVisible( isSecondaryVisible );
417 primary.actor.Unparent();
419 if( secondary.actor )
421 secondary.actor.Unparent();
423 if( mHighlightActor )
425 mHighlightActor.Unparent();
429 if( newGrabHandlePosition ||
430 newPrimaryHandlePosition ||
431 newSecondaryHandlePosition )
433 // Setup property notifications to find whether the handles leave the boundaries of the current display.
434 SetupActiveLayerPropertyNotifications();
437 if( mActiveCopyPastePopup )
440 mPopupSetNewPosition = true;
444 if( mCopyPastePopup.actor )
446 mCopyPastePopup.actor.HidePopup();
447 mPopupSetNewPosition = true;
452 void UpdatePositions( const Vector2& scrollOffset )
454 mCursor[PRIMARY_CURSOR].position += scrollOffset;
455 mCursor[SECONDARY_CURSOR].position += scrollOffset;
456 mHandle[ GRAB_HANDLE ].position += scrollOffset;
457 mHandle[ LEFT_SELECTION_HANDLE ].position += scrollOffset;
458 mHandle[ RIGHT_SELECTION_HANDLE ].position += scrollOffset;
459 mHighlightPosition += scrollOffset;
464 if ( !mCopyPastePopup.actor )
469 if( !mCopyPastePopup.actor.GetParent() )
471 mActiveLayer.Add( mCopyPastePopup.actor );
474 mCopyPastePopup.actor.RaiseAbove( mActiveLayer );
475 mCopyPastePopup.actor.ShowPopup();
478 void DeterminePositionPopup()
480 if( !mActiveCopyPastePopup )
485 // Retrieves the popup's size after relayout.
486 const Vector3 popupSize = Vector3( mCopyPastePopup.actor.GetRelayoutSize( Dimension::WIDTH ), mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT ), 0.0f );
488 if( mPopupSetNewPosition )
490 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
491 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
492 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
493 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
495 if( primaryHandle.active || secondaryHandle.active )
497 // Calculates the popup's position if selection handles are active.
498 const float minHandleXPosition = std::min( primaryHandle.position.x, secondaryHandle.position.x );
499 const float maxHandleXPosition = std::max( primaryHandle.position.x, secondaryHandle.position.x );
500 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
502 mCopyPastePopup.position.x = minHandleXPosition + ( ( maxHandleXPosition - minHandleXPosition ) * 0.5f );
503 mCopyPastePopup.position.y = -0.5f * popupSize.height - maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
507 // Calculates the popup's position if the grab handle is active.
508 mCopyPastePopup.position = Vector3( cursor.position.x, -0.5f * popupSize.height - grabHandle.size.height + cursor.position.y, 0.0f );
512 // Checks if there is enough space above the text control. If not it places the popup under it.
513 GetConstrainedPopupPosition( mCopyPastePopup.position, popupSize * AnchorPoint::CENTER, mActiveLayer, mBoundingBox );
515 SetUpPopupPositionNotifications();
517 mCopyPastePopup.actor.SetPosition( mCopyPastePopup.position );
518 mPopupSetNewPosition = false;
521 void PopupRelayoutComplete( Actor actor )
523 // Size negotiation for CopyPastePopup complete so can get the size and constrain position within bounding box.
525 DeterminePositionPopup();
528 void CreateCursor( Control& cursor, const Vector4& color )
530 cursor = Control::New();
531 cursor.SetBackgroundColor( color );
532 cursor.SetParentOrigin( ParentOrigin::TOP_LEFT ); // Need to set the default parent origin as CreateSolidColorActor() sets a different one.
533 cursor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
536 // Add or Remove cursor(s) from parent
539 if( mActiveCursor == ACTIVE_CURSOR_NONE )
543 mPrimaryCursor.Unparent();
545 if( mSecondaryCursor )
547 mSecondaryCursor.Unparent();
552 // Create Primary and or Secondary Cursor(s) if active and add to parent
553 if ( mActiveCursor == ACTIVE_CURSOR_PRIMARY ||
554 mActiveCursor == ACTIVE_CURSOR_BOTH )
556 if ( !mPrimaryCursor )
558 CreateCursor( mPrimaryCursor, mCursor[PRIMARY_CURSOR].color );
559 #ifdef DECORATOR_DEBUG
560 mPrimaryCursor.SetName( "PrimaryCursorActor" );
564 if( !mPrimaryCursor.GetParent() )
566 mActiveLayer.Add( mPrimaryCursor );
570 if ( mActiveCursor == ACTIVE_CURSOR_BOTH )
572 if ( !mSecondaryCursor )
574 CreateCursor( mSecondaryCursor, mCursor[SECONDARY_CURSOR].color );
575 #ifdef DECORATOR_DEBUG
576 mSecondaryCursor.SetName( "SecondaryCursorActor" );
580 if( !mSecondaryCursor.GetParent() )
582 mActiveLayer.Add( mSecondaryCursor );
587 if( mSecondaryCursor )
589 mSecondaryCursor.Unparent();
595 bool OnCursorBlinkTimerTick()
597 if( !mDelayCursorBlink )
600 if ( mPrimaryCursor )
602 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
604 if ( mSecondaryCursor )
606 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
609 mCursorBlinkStatus = !mCursorBlinkStatus;
614 mDelayCursorBlink = false;
620 void SetupTouchEvents()
622 mTapDetector = TapGestureDetector::New();
623 mTapDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnTap );
625 mPanGestureDetector = PanGestureDetector::New();
626 mPanGestureDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan );
629 void CreateActiveLayer()
633 mActiveLayer = Layer::New();
634 #ifdef DECORATOR_DEBUG
635 mActiveLayer.SetName ( "ActiveLayerActor" );
638 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER );
639 mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
640 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
642 // Add the active layer telling the controller it doesn't need clipping.
643 mController.AddDecoration( mActiveLayer, false );
646 mActiveLayer.RaiseToTop();
649 void SetSelectionHandleMarkerSize( HandleImpl& handle )
651 if( handle.markerActor )
653 handle.markerActor.SetSize( 0, handle.lineHeight );
657 void CreateGrabHandle()
659 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
660 if( !grabHandle.actor )
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 );
665 // Area that Grab handle responds to, larger than actual handle so easier to move
666 #ifdef DECORATOR_DEBUG
667 grabHandle.actor.SetName( "GrabHandleActor" );
668 if ( Dali::Internal::gLogFilter->IsEnabledFor( Debug::Verbose ) )
670 grabHandle.grabArea = Control::New();
671 Toolkit::Control control = Toolkit::Control::DownCast( grabHandle.grabArea );
672 control.SetBackgroundColor( Vector4( 1.0f, 1.0f, 1.0f, 0.5f ) );
673 grabHandle.grabArea.SetName( "GrabArea" );
677 grabHandle.grabArea = Actor::New();
678 grabHandle.grabArea.SetName( "GrabArea" );
681 grabHandle.grabArea = Actor::New();
684 grabHandle.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
685 grabHandle.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
686 grabHandle.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
687 grabHandle.grabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
688 grabHandle.actor.Add( grabHandle.grabArea );
689 grabHandle.actor.SetColor( mHandleColor );
691 grabHandle.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnGrabHandleTouched );
692 mTapDetector.Attach( grabHandle.grabArea );
693 mPanGestureDetector.Attach( grabHandle.grabArea );
695 mActiveLayer.Add( grabHandle.actor );
698 if( !grabHandle.actor.GetParent() )
700 mActiveLayer.Add( grabHandle.actor );
704 void CreateHandleMarker( HandleImpl& handle, Image& image, HandleType handleType )
708 handle.markerActor = ImageView::New( image );
709 handle.markerActor.SetColor( mHandleColor );
710 handle.actor.Add( handle.markerActor );
712 handle.markerActor.SetResizePolicy ( ResizePolicy::FIXED, Dimension::HEIGHT );
714 if( LEFT_SELECTION_HANDLE == handleType )
716 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_RIGHT );
717 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_RIGHT );
719 else if( RIGHT_SELECTION_HANDLE == handleType )
721 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_LEFT );
722 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
727 void CreateSelectionHandles()
729 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
732 primary.actor = ImageView::New( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
733 #ifdef DECORATOR_DEBUG
734 primary.actor.SetName("SelectionHandleOne");
736 primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
737 GetImpl( primary.actor ).SetDepthIndex( DepthIndex::DECORATION );
738 primary.actor.SetColor( mHandleColor );
740 primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
741 #ifdef DECORATOR_DEBUG
742 primary.grabArea.SetName("SelectionHandleOneGrabArea");
744 primary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
745 primary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
746 primary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
747 primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
749 mTapDetector.Attach( primary.grabArea );
750 mPanGestureDetector.Attach( primary.grabArea );
751 primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched );
753 primary.actor.Add( primary.grabArea );
755 CreateHandleMarker( primary, mHandleImages[LEFT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], LEFT_SELECTION_HANDLE );
758 if( !primary.actor.GetParent() )
760 mActiveLayer.Add( primary.actor );
763 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
764 if( !secondary.actor )
766 secondary.actor = ImageView::New( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
767 #ifdef DECORATOR_DEBUG
768 secondary.actor.SetName("SelectionHandleTwo");
770 secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text.
771 GetImpl( secondary.actor ).SetDepthIndex( DepthIndex::DECORATION );
772 secondary.actor.SetColor( mHandleColor );
774 secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
775 #ifdef DECORATOR_DEBUG
776 secondary.grabArea.SetName("SelectionHandleTwoGrabArea");
778 secondary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
779 secondary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
780 secondary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
781 secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
783 mTapDetector.Attach( secondary.grabArea );
784 mPanGestureDetector.Attach( secondary.grabArea );
785 secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched );
787 secondary.actor.Add( secondary.grabArea );
789 CreateHandleMarker( secondary, mHandleImages[RIGHT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], RIGHT_SELECTION_HANDLE );
792 if( !secondary.actor.GetParent() )
794 mActiveLayer.Add( secondary.actor );
798 void CalculateHandleWorldCoordinates( HandleImpl& handle, Vector2& position )
800 // Gets the world position of the active layer. The active layer is where the handles are added.
801 const Vector3 parentWorldPosition = mActiveLayer.GetCurrentWorldPosition();
803 // The grab handle position in world coords.
804 // The active layer's world position is the center of the active layer. The origin of the
805 // coord system of the handles is the top left of the active layer.
806 position.x = parentWorldPosition.x - 0.5f * mControlSize.width + handle.position.x;
807 position.y = parentWorldPosition.y - 0.5f * mControlSize.height + handle.position.y;
810 void SetGrabHandlePosition()
812 // Reference to the grab handle.
813 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
815 // Transforms the handle position into world coordinates.
816 // @note This is not the same value as grabHandle.actor.GetCurrentWorldPosition()
817 // as it's transforming the handle's position set by the text-controller and not
818 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
819 // retrieves the position of the center of the actor but the handle's position set
820 // by the text controller is not the center of the actor.
821 Vector2 grabHandleWorldPosition;
822 CalculateHandleWorldCoordinates( grabHandle, grabHandleWorldPosition );
824 // Check if the grab handle exceeds the boundaries of the decoration box.
825 // At the moment only the height is checked for the grab handle.
827 grabHandle.verticallyFlipped = ( grabHandle.verticallyFlippedPreferred &&
828 ( ( grabHandleWorldPosition.y - grabHandle.size.height ) > mBoundingBox.y ) ) ||
829 ( grabHandleWorldPosition.y + grabHandle.lineHeight + grabHandle.size.height > mBoundingBox.w );
831 // The grab handle 'y' position in local coords.
832 // If the grab handle exceeds the bottom of the decoration box,
833 // set the 'y' position to the top of the line.
834 // The SetGrabHandleImage() method will change the orientation.
835 const float yLocalPosition = grabHandle.verticallyFlipped ? grabHandle.position.y : grabHandle.position.y + grabHandle.lineHeight;
837 grabHandle.actor.SetPosition( grabHandle.position.x + floor( 0.5f * mCursorWidth ),
838 yLocalPosition ); // TODO : Fix for multiline.
841 void SetSelectionHandlePosition( HandleType type )
843 const bool isPrimaryHandle = LEFT_SELECTION_HANDLE == type;
845 // Reference to the selection handle.
846 HandleImpl& handle = mHandle[type];
848 // Transforms the handle position into world coordinates.
849 // @note This is not the same value as handle.actor.GetCurrentWorldPosition()
850 // as it's transforming the handle's position set by the text-controller and not
851 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
852 // retrieves the position of the center of the actor but the handle's position set
853 // by the text controller is not the center of the actor.
854 Vector2 handleWorldPosition;
855 CalculateHandleWorldCoordinates( handle, handleWorldPosition );
857 // Whether to flip the handle (horizontally).
858 bool flipHandle = isPrimaryHandle ? mFlipLeftSelectionHandleDirection : mFlipRightSelectionHandleDirection;
860 // Whether to flip the handles if they are crossed.
861 bool crossFlip = false;
862 if( mFlipSelectionHandlesOnCross || !mHandlePanning )
864 crossFlip = mHandleCurrentCrossed;
867 // Does not flip if both conditions are true (double flip)
868 flipHandle = flipHandle != ( crossFlip || mHandlePreviousCrossed );
870 // Will flip the handles vertically if the user prefers it.
871 bool verticallyFlippedPreferred = handle.verticallyFlippedPreferred;
873 if( crossFlip || mHandlePreviousCrossed )
875 if( isPrimaryHandle )
877 verticallyFlippedPreferred = mHandle[RIGHT_SELECTION_HANDLE].verticallyFlippedPreferred;
881 verticallyFlippedPreferred = mHandle[LEFT_SELECTION_HANDLE].verticallyFlippedPreferred;
885 // Check if the selection handle exceeds the boundaries of the decoration box.
886 const bool exceedsLeftEdge = ( isPrimaryHandle ? !flipHandle : flipHandle ) && ( handleWorldPosition.x - handle.size.width < mBoundingBox.x );
887 const bool exceedsRightEdge = ( isPrimaryHandle ? flipHandle : !flipHandle ) && ( handleWorldPosition.x + handle.size.width > mBoundingBox.z );
889 // Does not flip if both conditions are true (double flip)
890 flipHandle = flipHandle != ( exceedsLeftEdge || exceedsRightEdge );
894 if( !handle.horizontallyFlipped )
896 // Change the anchor point to flip the image.
897 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_LEFT : AnchorPoint::TOP_RIGHT );
899 handle.horizontallyFlipped = true;
904 if( handle.horizontallyFlipped )
906 // Reset the anchor point.
907 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_RIGHT : AnchorPoint::TOP_LEFT );
909 handle.horizontallyFlipped = false;
913 // Whether to flip the handle vertically.
914 handle.verticallyFlipped = ( verticallyFlippedPreferred &&
915 ( ( handleWorldPosition.y - handle.size.height ) > mBoundingBox.y ) ) ||
916 ( handleWorldPosition.y + handle.lineHeight + handle.size.height > mBoundingBox.w );
918 // The primary selection handle 'y' position in local coords.
919 // If the handle exceeds the bottom of the decoration box,
920 // set the 'y' position to the top of the line.
921 // The SetHandleImage() method will change the orientation.
922 const float yLocalPosition = handle.verticallyFlipped ? handle.position.y : handle.position.y + handle.lineHeight;
924 handle.actor.SetPosition( handle.position.x,
925 yLocalPosition ); // TODO : Fix for multiline.
928 void SetHandleImage( HandleType type )
930 HandleImpl& handle = mHandle[type];
932 HandleType markerType = HANDLE_TYPE_COUNT;
933 // If the selection handle is flipped it chooses the image of the other selection handle. Does nothing for the grab handle.
934 if( LEFT_SELECTION_HANDLE == type )
936 type = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE;
937 markerType = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE_MARKER : LEFT_SELECTION_HANDLE_MARKER;
939 else if( RIGHT_SELECTION_HANDLE == type )
941 type = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE : RIGHT_SELECTION_HANDLE;
942 markerType = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE_MARKER : RIGHT_SELECTION_HANDLE_MARKER;
945 // Chooses between the released or pressed image. It checks whether the pressed image exists.
948 const HandleImageType imageType = ( handle.pressed ? ( mHandleImages[type][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
950 handle.actor.SetImage( mHandleImages[type][imageType] );
953 if( HANDLE_TYPE_COUNT != markerType )
955 if( handle.markerActor )
957 const HandleImageType markerImageType = ( handle.pressed ? ( mHandleImages[markerType][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
958 handle.markerActor.SetImage( mHandleImages[markerType][markerImageType] );
962 // Whether to flip the handle vertically.
965 handle.actor.SetOrientation( handle.verticallyFlipped ? ANGLE_180 : ANGLE_0, Vector3::XAXIS );
969 void CreateHighlight()
971 if( !mHighlightActor )
973 mHighlightActor = Actor::New();
975 #ifdef DECORATOR_DEBUG
976 mHighlightActor.SetName( "HighlightActor" );
978 mHighlightActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
979 mHighlightActor.SetSize( 1.0f, 1.0f );
980 mHighlightActor.SetColor( mHighlightColor );
981 mHighlightActor.SetColorMode( USE_OWN_COLOR );
984 // Add the highlight box telling the controller it needs clipping.
985 mController.AddDecoration( mHighlightActor, true );
988 void UpdateHighlight()
990 if ( mHighlightActor )
992 if( !mHighlightQuadList.empty() )
994 Vector< Vector2 > vertices;
995 Vector< unsigned int> indices;
998 std::vector<QuadCoordinates>::iterator iter = mHighlightQuadList.begin();
999 std::vector<QuadCoordinates>::iterator endIter = mHighlightQuadList.end();
1001 for( std::size_t v = 0; iter != endIter; ++iter,v+=4 )
1003 QuadCoordinates& quad = *iter;
1006 vertex.x = quad.min.x;
1007 vertex.y = quad.min.y;
1008 vertices.PushBack( vertex );
1011 vertex.x = quad.max.x;
1012 vertex.y = quad.min.y;
1013 vertices.PushBack( vertex );
1015 // bottom-left (v+2)
1016 vertex.x = quad.min.x;
1017 vertex.y = quad.max.y;
1018 vertices.PushBack( vertex );
1020 // bottom-right (v+3)
1021 vertex.x = quad.max.x;
1022 vertex.y = quad.max.y;
1023 vertices.PushBack( vertex );
1025 // triangle A (3, 1, 0)
1026 indices.PushBack( v + 3 );
1027 indices.PushBack( v + 1 );
1028 indices.PushBack( v );
1030 // triangle B (0, 2, 3)
1031 indices.PushBack( v );
1032 indices.PushBack( v + 2 );
1033 indices.PushBack( v + 3 );
1038 mQuadVertices.SetSize( vertices.Size() );
1042 mQuadVertices = PropertyBuffer::New( mQuadVertexFormat, vertices.Size() );
1047 mQuadIndices.SetSize( indices.Size() );
1051 mQuadIndices = PropertyBuffer::New( mQuadIndexFormat, indices.Size() );
1054 mQuadVertices.SetData( &vertices[ 0 ] );
1055 mQuadIndices.SetData( &indices[ 0 ] );
1057 if( !mQuadGeometry )
1059 mQuadGeometry = Geometry::New();
1060 mQuadGeometry.AddVertexBuffer( mQuadVertices );
1062 mQuadGeometry.SetIndexBuffer( mQuadIndices );
1064 if( !mHighlightRenderer )
1066 mHighlightRenderer = Dali::Renderer::New( mQuadGeometry, mHighlightMaterial );
1067 mHighlightActor.AddRenderer( mHighlightRenderer );
1071 mHighlightActor.SetPosition( mHighlightPosition.x,
1072 mHighlightPosition.y );
1074 mHighlightQuadList.clear();
1076 if( mHighlightRenderer )
1078 mHighlightRenderer.SetDepthIndex( mTextDepth - 2u ); // text is rendered at mTextDepth and text's shadow at mTextDepth -1u.
1083 void OnTap( Actor actor, const TapGesture& tap )
1085 if( actor == mHandle[GRAB_HANDLE].actor )
1091 void DoPan( HandleImpl& handle, HandleType type, const PanGesture& gesture )
1093 if( Gesture::Started == gesture.state )
1095 handle.grabDisplacementX = handle.grabDisplacementY = 0;
1098 handle.grabDisplacementX += gesture.displacement.x;
1099 handle.grabDisplacementY += gesture.displacement.y;
1101 const float x = handle.position.x + handle.grabDisplacementX;
1102 const float y = handle.position.y + handle.lineHeight*0.5f + handle.grabDisplacementY;
1104 if( Gesture::Started == gesture.state ||
1105 Gesture::Continuing == gesture.state )
1108 mController.GetTargetSize( targetSize );
1110 if( x < mScrollThreshold )
1112 mScrollDirection = SCROLL_RIGHT;
1113 mHandleScrolling = type;
1116 else if( x > targetSize.width - mScrollThreshold )
1118 mScrollDirection = SCROLL_LEFT;
1119 mHandleScrolling = type;
1124 mHandleScrolling = HANDLE_TYPE_COUNT;
1126 mController.DecorationEvent( type, HANDLE_PRESSED, x, y );
1129 mHandlePanning = true;
1131 else if( Gesture::Finished == gesture.state ||
1132 Gesture::Cancelled == gesture.state )
1135 ( mScrollTimer.IsRunning() || mNotifyEndOfScroll ) )
1137 mNotifyEndOfScroll = false;
1138 mHandleScrolling = HANDLE_TYPE_COUNT;
1140 mController.DecorationEvent( type, HANDLE_STOP_SCROLLING, x, y );
1144 mController.DecorationEvent( type, HANDLE_RELEASED, x, y );
1147 handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_RELEASED] );
1148 handle.pressed = false;
1150 mHandlePanning = false;
1154 void OnPan( Actor actor, const PanGesture& gesture )
1156 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1157 HandleImpl& primarySelectionHandle = mHandle[LEFT_SELECTION_HANDLE];
1158 HandleImpl& secondarySelectionHandle = mHandle[RIGHT_SELECTION_HANDLE];
1160 if( actor == grabHandle.grabArea )
1162 DoPan( grabHandle, GRAB_HANDLE, gesture );
1164 else if( actor == primarySelectionHandle.grabArea )
1166 DoPan( primarySelectionHandle, LEFT_SELECTION_HANDLE, gesture );
1168 else if( actor == secondarySelectionHandle.grabArea )
1170 DoPan( secondarySelectionHandle, RIGHT_SELECTION_HANDLE, gesture );
1174 bool OnGrabHandleTouched( Actor actor, const TouchEvent& event )
1176 // Switch between pressed/release grab-handle images
1177 if( event.GetPointCount() > 0 &&
1178 mHandle[GRAB_HANDLE].actor )
1180 const TouchPoint& point = event.GetPoint(0);
1182 if( TouchPoint::Down == point.state )
1184 mHandle[GRAB_HANDLE].pressed = true;
1186 else if( ( TouchPoint::Up == point.state ) ||
1187 ( TouchPoint::Interrupted == point.state ) )
1189 mHandle[GRAB_HANDLE].pressed = false;
1192 SetHandleImage( GRAB_HANDLE );
1195 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1199 bool OnHandleOneTouched( Actor actor, const TouchEvent& event )
1201 // Switch between pressed/release selection handle images
1202 if( event.GetPointCount() > 0 &&
1203 mHandle[LEFT_SELECTION_HANDLE].actor )
1205 const TouchPoint& point = event.GetPoint(0);
1207 if( TouchPoint::Down == point.state )
1209 mHandle[LEFT_SELECTION_HANDLE].pressed = true;
1211 else if( ( TouchPoint::Up == point.state ) ||
1212 ( TouchPoint::Interrupted == point.state ) )
1214 mHandle[LEFT_SELECTION_HANDLE].pressed = false;
1215 mHandlePreviousCrossed = mHandleCurrentCrossed;
1216 mHandlePanning = false;
1219 SetHandleImage( LEFT_SELECTION_HANDLE );
1222 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1226 bool OnHandleTwoTouched( Actor actor, const TouchEvent& event )
1228 // Switch between pressed/release selection handle images
1229 if( event.GetPointCount() > 0 &&
1230 mHandle[RIGHT_SELECTION_HANDLE].actor )
1232 const TouchPoint& point = event.GetPoint(0);
1234 if( TouchPoint::Down == point.state )
1236 mHandle[RIGHT_SELECTION_HANDLE].pressed = true;
1238 else if( ( TouchPoint::Up == point.state ) ||
1239 ( TouchPoint::Interrupted == point.state ) )
1241 mHandle[RIGHT_SELECTION_HANDLE].pressed = false;
1242 mHandlePreviousCrossed = mHandleCurrentCrossed;
1243 mHandlePanning = false;
1246 SetHandleImage( RIGHT_SELECTION_HANDLE );
1249 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1253 void HandleResetPosition( PropertyNotification& source )
1255 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1257 if( grabHandle.active )
1259 // Sets the grab handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1260 SetGrabHandlePosition();
1262 // Sets the grab handle image according if it's pressed, flipped, etc.
1263 SetHandleImage( GRAB_HANDLE );
1267 // Sets the primary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1268 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
1270 // Sets the primary handle image according if it's pressed, flipped, etc.
1271 SetHandleImage( LEFT_SELECTION_HANDLE );
1273 // Sets the secondary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1274 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
1276 // Sets the secondary handle image according if it's pressed, flipped, etc.
1277 SetHandleImage( RIGHT_SELECTION_HANDLE );
1281 void SetupActiveLayerPropertyNotifications()
1288 // Vertical notifications.
1290 // Disconnect any previous connected callback.
1291 if( mVerticalLessThanNotification )
1293 mVerticalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1294 mActiveLayer.RemovePropertyNotification( mVerticalLessThanNotification );
1297 if( mVerticalGreaterThanNotification )
1299 mVerticalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1300 mActiveLayer.RemovePropertyNotification( mVerticalGreaterThanNotification );
1303 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1304 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1305 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1307 if( grabHandle.active )
1309 if( grabHandle.verticallyFlipped )
1311 // The grab handle is vertically flipped. Never is going to exceed the bottom edje of the display.
1312 mVerticalGreaterThanNotification.Reset();
1314 // The vertical distance from the center of the active layer to the top edje of the display.
1315 const float topHeight = 0.5f * mControlSize.height - grabHandle.position.y + grabHandle.size.height;
1317 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1318 LessThanCondition( mBoundingBox.y + topHeight ) );
1320 // Notifies the change from false to true and from true to false.
1321 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1323 // Connects the signals with the callbacks.
1324 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1328 // The grab handle is not vertically flipped. Never is going to exceed the top edje of the display.
1329 mVerticalLessThanNotification.Reset();
1331 // The vertical distance from the center of the active layer to the bottom edje of the display.
1332 const float bottomHeight = -0.5f * mControlSize.height + grabHandle.position.y + grabHandle.lineHeight + grabHandle.size.height;
1334 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1335 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1337 // Notifies the change from false to true and from true to false.
1338 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1340 // Connects the signals with the callbacks.
1341 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1344 else // The selection handles are active
1346 if( primaryHandle.verticallyFlipped && secondaryHandle.verticallyFlipped )
1348 // Both selection handles are vertically flipped. Never are going to exceed the bottom edje of the display.
1349 mVerticalGreaterThanNotification.Reset();
1351 // The vertical distance from the center of the active layer to the top edje of the display.
1352 const float topHeight = 0.5f * mControlSize.height + std::max( -primaryHandle.position.y + primaryHandle.size.height, -secondaryHandle.position.y + secondaryHandle.size.height );
1354 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1355 LessThanCondition( mBoundingBox.y + topHeight ) );
1357 // Notifies the change from false to true and from true to false.
1358 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1360 // Connects the signals with the callbacks.
1361 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1363 else if( !primaryHandle.verticallyFlipped && !secondaryHandle.verticallyFlipped )
1365 // Both selection handles aren't vertically flipped. Never are going to exceed the top edje of the display.
1366 mVerticalLessThanNotification.Reset();
1368 // The vertical distance from the center of the active layer to the bottom edje of the display.
1369 const float bottomHeight = -0.5f * mControlSize.height + std::max( primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height,
1370 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height );
1372 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1373 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1375 // Notifies the change from false to true and from true to false.
1376 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1378 // Connects the signals with the callbacks.
1379 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1383 // Only one of the selection handles is vertically flipped. Both vertical notifications are needed.
1385 // The vertical distance from the center of the active layer to the top edje of the display.
1386 const float topHeight = 0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1387 -primaryHandle.position.y + primaryHandle.size.height :
1388 -secondaryHandle.position.y + secondaryHandle.size.height );
1390 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1391 LessThanCondition( mBoundingBox.y + topHeight ) );
1393 // Notifies the change from false to true and from true to false.
1394 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1396 // Connects the signals with the callbacks.
1397 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1399 // The vertical distance from the center of the active layer to the bottom edje of the display.
1400 const float bottomHeight = -0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1401 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height :
1402 primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height );
1404 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1405 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1407 // Notifies the change from false to true and from true to false.
1408 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1410 // Connects the signals with the callbacks.
1411 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1415 // Horizontal notifications.
1417 // Disconnect any previous connected callback.
1418 if( mHorizontalLessThanNotification )
1420 mHorizontalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1421 mActiveLayer.RemovePropertyNotification( mHorizontalLessThanNotification );
1424 if( mHorizontalGreaterThanNotification )
1426 mHorizontalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1427 mActiveLayer.RemovePropertyNotification( mHorizontalGreaterThanNotification );
1430 if( primaryHandle.active || secondaryHandle.active )
1432 // The horizontal distance from the center of the active layer to the left edje of the display.
1433 const float leftWidth = 0.5f * mControlSize.width + std::max( -primaryHandle.position.x + primaryHandle.size.width,
1434 -secondaryHandle.position.x + secondaryHandle.size.width );
1436 mHorizontalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1437 LessThanCondition( mBoundingBox.x + leftWidth ) );
1439 // Notifies the change from false to true and from true to false.
1440 mHorizontalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1442 // Connects the signals with the callbacks.
1443 mHorizontalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1445 // The horizontal distance from the center of the active layer to the right edje of the display.
1446 const float rightWidth = -0.5f * mControlSize.width + std::max( primaryHandle.position.x + primaryHandle.size.width,
1447 secondaryHandle.position.x + secondaryHandle.size.width );
1449 mHorizontalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1450 GreaterThanCondition( mBoundingBox.z - rightWidth ) );
1452 // Notifies the change from false to true and from true to false.
1453 mHorizontalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1455 // Connects the signals with the callbacks.
1456 mHorizontalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1462 float AlternatePopUpPositionRelativeToCursor()
1464 float alternativePosition = 0.0f;
1466 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1468 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1469 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1470 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1471 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
1473 if( primaryHandle.active || secondaryHandle.active )
1475 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
1476 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
1480 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + grabHandle.size.height + cursor.position.y;
1483 return alternativePosition;
1486 void PopUpLeavesVerticalBoundary( PropertyNotification& source )
1488 float alternativeYPosition = 0.0f;
1489 // todo use AlternatePopUpPositionRelativeToSelectionHandles() if text is highlighted
1490 // if can't be positioned above, then position below row.
1491 alternativeYPosition = AlternatePopUpPositionRelativeToCursor();
1493 mCopyPastePopup.actor.SetY( alternativeYPosition );
1496 void SetUpPopupPositionNotifications()
1498 // Note Property notifications ignore any set anchor point so conditions must allow for this. Default is Top Left.
1500 // Exceeding vertical boundary
1502 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1504 PropertyNotification verticalExceedNotification = mCopyPastePopup.actor.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1505 OutsideCondition( mBoundingBox.y + popupHeight * 0.5f,
1506 mBoundingBox.w - popupHeight * 0.5f ) );
1508 verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary );
1511 void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, const Vector3& popupDistanceFromAnchorPoint, Actor parent, const Vector4& boundingRectangleWorld )
1513 DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() )
1515 // Parent must already by added to Stage for these Get calls to work
1516 const Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize() * parent.GetCurrentAnchorPoint();
1517 const Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition; // Parent World position plus popup local position gives World Position
1519 // Calculate distance to move popup (in local space) so fits within the boundary
1520 float xOffSetToKeepWithinBounds = 0.0f;
1521 if( popupWorldPosition.x - popupDistanceFromAnchorPoint.x < boundingRectangleWorld.x )
1523 xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x );
1525 else if( popupWorldPosition.x + popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z )
1527 xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x + popupDistanceFromAnchorPoint.x );
1530 // Ensure initial display of Popup is in alternative position if can not fit above. As Property notification will be a frame behind.
1531 if( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y )
1533 requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor();
1536 requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds;
1538 // Prevent pixel mis-alignment by rounding down.
1539 requiredPopupPosition.x = floor( requiredPopupPosition.x );
1540 requiredPopupPosition.y = floor( requiredPopupPosition.y );
1543 void SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1545 HandleImpl& handle = mHandle[handleType];
1546 handle.size = Size( image.GetWidth(), image.GetHeight() );
1548 mHandleImages[handleType][handleImageType] = image;
1551 void SetScrollThreshold( float threshold )
1553 mScrollThreshold = threshold;
1556 float GetScrollThreshold() const
1558 return mScrollThreshold;
1561 void SetScrollSpeed( float speed )
1563 mScrollSpeed = speed;
1564 mScrollDistance = speed * SCROLL_TICK_INTERVAL * TO_SECONDS;
1567 float GetScrollSpeed() const
1569 return mScrollSpeed;
1572 void NotifyEndOfScroll()
1578 mNotifyEndOfScroll = true;
1583 * Creates and starts a timer to scroll the text when handles are close to the edges of the text.
1585 * It only starts the timer if it's already created.
1587 void StartScrollTimer()
1591 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1592 mScrollTimer.TickSignal().Connect( this, &Decorator::Impl::OnScrollTimerTick );
1595 if( !mScrollTimer.IsRunning() )
1597 mScrollTimer.Start();
1602 * Stops the timer used to scroll the text.
1604 void StopScrollTimer()
1608 mScrollTimer.Stop();
1613 * Callback called by the timer used to scroll the text.
1615 * It calculates and sets a new scroll position.
1617 bool OnScrollTimerTick()
1619 if( HANDLE_TYPE_COUNT != mHandleScrolling )
1621 mController.DecorationEvent( mHandleScrolling,
1623 mScrollDirection == SCROLL_RIGHT ? mScrollDistance : -mScrollDistance,
1630 ControllerInterface& mController;
1632 TapGestureDetector mTapDetector;
1633 PanGestureDetector mPanGestureDetector;
1634 Timer mCursorBlinkTimer; ///< Timer to signal cursor to blink
1635 Timer mScrollTimer; ///< Timer used to scroll the text when the grab handle is moved close to the edges.
1637 Layer mActiveLayer; ///< Layer for active handles and alike that ensures they are above all else.
1638 PropertyNotification mVerticalLessThanNotification; ///< Notifies when the 'y' coord of the active layer is less than a given value.
1639 PropertyNotification mVerticalGreaterThanNotification; ///< Notifies when the 'y' coord of the active layer is grater than a given value.
1640 PropertyNotification mHorizontalLessThanNotification; ///< Notifies when the 'x' coord of the active layer is less than a given value.
1641 PropertyNotification mHorizontalGreaterThanNotification; ///< Notifies when the 'x' coord of the active layer is grater than a given value.
1642 Control mPrimaryCursor;
1643 Control mSecondaryCursor;
1645 Actor mHighlightActor; ///< Actor to display highlight
1646 Renderer mHighlightRenderer;
1647 Material mHighlightMaterial; ///< Material used for highlight
1648 Property::Map mQuadVertexFormat;
1649 Property::Map mQuadIndexFormat;
1650 PopupImpl mCopyPastePopup;
1651 TextSelectionPopup::Buttons mEnabledPopupButtons; /// Bit mask of currently enabled Popup buttons
1652 TextSelectionPopupCallbackInterface& mTextSelectionPopupCallbackInterface;
1654 Image mHandleImages[HANDLE_TYPE_COUNT][HANDLE_IMAGE_TYPE_COUNT];
1655 Vector4 mHandleColor;
1657 CursorImpl mCursor[CURSOR_COUNT];
1658 HandleImpl mHandle[HANDLE_TYPE_COUNT];
1660 PropertyBuffer mQuadVertices;
1661 PropertyBuffer mQuadIndices;
1662 Geometry mQuadGeometry;
1663 QuadContainer mHighlightQuadList; ///< Sub-selections that combine to create the complete selection highlight
1665 Vector4 mBoundingBox; ///< The bounding box in world coords.
1666 Vector4 mHighlightColor; ///< Color of the highlight
1667 Vector2 mHighlightPosition; ///< The position of the highlight actor.
1668 Vector2 mControlSize; ///< The control's size. Set by the Relayout.
1670 unsigned int mActiveCursor;
1671 unsigned int mCursorBlinkInterval;
1672 float mCursorBlinkDuration;
1673 float mCursorWidth; ///< The width of the cursors in pixels.
1674 HandleType mHandleScrolling; ///< The handle which is scrolling.
1675 ScrollDirection mScrollDirection; ///< The direction of the scroll.
1676 float mScrollThreshold; ///< Defines a square area inside the control, close to the edge. A cursor entering this area will trigger scroll events.
1677 float mScrollSpeed; ///< The scroll speed in pixels per second.
1678 float mScrollDistance; ///< Distance the text scrolls during a scroll interval.
1679 int mTextDepth; ///< The depth used to render the text.
1681 bool mActiveCopyPastePopup : 1;
1682 bool mPopupSetNewPosition : 1;
1683 bool mCursorBlinkStatus : 1; ///< Flag to switch between blink on and blink off.
1684 bool mDelayCursorBlink : 1; ///< Used to avoid cursor blinking when entering text.
1685 bool mPrimaryCursorVisible : 1; ///< Whether the primary cursor is visible.
1686 bool mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible.
1687 bool mFlipSelectionHandlesOnCross : 1; ///< Whether to flip the selection handles as soon as they cross.
1688 bool mFlipLeftSelectionHandleDirection : 1; ///< Whether to flip the left selection handle image because of the character's direction.
1689 bool mFlipRightSelectionHandleDirection : 1; ///< Whether to flip the right selection handle image because of the character's direction.
1690 bool mHandlePanning : 1; ///< Whether any of the handles is moving.
1691 bool mHandleCurrentCrossed : 1; ///< Whether the handles are crossed.
1692 bool mHandlePreviousCrossed : 1; ///< Whether the handles where crossed at the last handle touch up.
1693 bool mNotifyEndOfScroll : 1; ///< Whether to notify the end of the scroll.
1696 DecoratorPtr Decorator::New( ControllerInterface& controller,
1697 TextSelectionPopupCallbackInterface& callbackInterface )
1699 return DecoratorPtr( new Decorator( controller,
1700 callbackInterface ) );
1703 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
1705 LocalToWorldCoordinatesBoundingBox( boundingBox, mImpl->mBoundingBox );
1708 void Decorator::GetBoundingBox( Rect<int>& boundingBox ) const
1710 WorldToLocalCoordinatesBoundingBox( mImpl->mBoundingBox, boundingBox );
1713 void Decorator::Relayout( const Vector2& size )
1715 mImpl->Relayout( size );
1718 void Decorator::UpdatePositions( const Vector2& scrollOffset )
1720 mImpl->UpdatePositions( scrollOffset );
1725 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
1727 mImpl->mActiveCursor = activeCursor;
1730 unsigned int Decorator::GetActiveCursor() const
1732 return mImpl->mActiveCursor;
1735 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
1737 mImpl->mCursor[cursor].position.x = x;
1738 mImpl->mCursor[cursor].position.y = y;
1739 mImpl->mCursor[cursor].cursorHeight = cursorHeight;
1740 mImpl->mCursor[cursor].lineHeight = lineHeight;
1743 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
1745 x = mImpl->mCursor[cursor].position.x;
1746 y = mImpl->mCursor[cursor].position.y;
1747 cursorHeight = mImpl->mCursor[cursor].cursorHeight;
1748 lineHeight = mImpl->mCursor[cursor].lineHeight;
1751 const Vector2& Decorator::GetPosition( Cursor cursor ) const
1753 return mImpl->mCursor[cursor].position;
1756 void Decorator::SetCursorColor( Cursor cursor, const Dali::Vector4& color )
1758 mImpl->mCursor[cursor].color = color;
1761 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
1763 return mImpl->mCursor[cursor].color;
1766 void Decorator::StartCursorBlink()
1768 if ( !mImpl->mCursorBlinkTimer )
1770 mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
1771 mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
1774 if ( !mImpl->mCursorBlinkTimer.IsRunning() )
1776 mImpl->mCursorBlinkTimer.Start();
1780 void Decorator::StopCursorBlink()
1782 if ( mImpl->mCursorBlinkTimer )
1784 mImpl->mCursorBlinkTimer.Stop();
1787 mImpl->mCursorBlinkStatus = true; // Keep cursor permanently shown
1790 void Decorator::DelayCursorBlink()
1792 mImpl->mCursorBlinkStatus = true; // Show cursor for a bit longer
1793 mImpl->mDelayCursorBlink = true;
1796 void Decorator::SetCursorBlinkInterval( float seconds )
1798 mImpl->mCursorBlinkInterval = static_cast<unsigned int>( seconds * TO_MILLISECONDS ); // Convert to milliseconds
1801 float Decorator::GetCursorBlinkInterval() const
1803 return static_cast<float>( mImpl->mCursorBlinkInterval ) * TO_SECONDS;
1806 void Decorator::SetCursorBlinkDuration( float seconds )
1808 mImpl->mCursorBlinkDuration = seconds;
1811 float Decorator::GetCursorBlinkDuration() const
1813 return mImpl->mCursorBlinkDuration;
1816 void Decorator::SetCursorWidth( int width )
1818 mImpl->mCursorWidth = static_cast<float>( width );
1821 int Decorator::GetCursorWidth() const
1823 return static_cast<int>( mImpl->mCursorWidth );
1828 void Decorator::SetHandleActive( HandleType handleType, bool active )
1830 mImpl->mHandle[handleType].active = active;
1834 if( ( LEFT_SELECTION_HANDLE == handleType ) || ( RIGHT_SELECTION_HANDLE == handleType ) )
1836 mImpl->mHandlePreviousCrossed = false;
1839 // TODO: this is a work-around.
1840 // The problem is the handle actor does not receive the touch event with the Interrupt
1841 // state when the power button is pressed and the application goes to background.
1842 mImpl->mHandle[handleType].pressed = false;
1843 Image imageReleased = mImpl->mHandleImages[handleType][HANDLE_IMAGE_RELEASED];
1844 ImageView imageView = mImpl->mHandle[handleType].actor;
1845 if( imageReleased && imageView )
1847 imageView.SetImage( imageReleased );
1853 bool Decorator::IsHandleActive( HandleType handleType ) const
1855 return mImpl->mHandle[handleType].active ;
1858 void Decorator::SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1860 mImpl->SetHandleImage( handleType, handleImageType, image );
1863 Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType handleImageType ) const
1865 return mImpl->mHandleImages[handleType][handleImageType];
1868 void Decorator::SetHandleColor( const Vector4& color )
1870 mImpl->mHandleColor = color;
1873 const Vector4& Decorator::GetHandleColor() const
1875 return mImpl->mHandleColor;
1878 void Decorator::SetPosition( HandleType handleType, float x, float y, float height )
1880 // Adjust handle's displacement
1881 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1883 handle.grabDisplacementX -= x - handle.position.x;
1884 handle.grabDisplacementY -= y - handle.position.y;
1886 handle.position.x = x;
1887 handle.position.y = y;
1888 handle.lineHeight = height;
1891 void Decorator::GetPosition( HandleType handleType, float& x, float& y, float& height ) const
1893 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1895 x = handle.position.x;
1896 y = handle.position.y;
1897 height = handle.lineHeight;
1900 const Vector2& Decorator::GetPosition( HandleType handleType ) const
1902 return mImpl->mHandle[handleType].position;
1905 void Decorator::FlipHandleVertically( HandleType handleType, bool flip )
1907 mImpl->mHandle[handleType].verticallyFlippedPreferred = flip;
1910 bool Decorator::IsHandleVerticallyFlipped( HandleType handleType ) const
1912 return mImpl->mHandle[handleType].verticallyFlippedPreferred;
1915 void Decorator::FlipSelectionHandlesOnCrossEnabled( bool enable )
1917 mImpl->mFlipSelectionHandlesOnCross = enable;
1920 void Decorator::SetSelectionHandleFlipState( bool indicesSwapped, bool left, bool right )
1922 mImpl->mHandleCurrentCrossed = indicesSwapped;
1923 mImpl->mFlipLeftSelectionHandleDirection = left;
1924 mImpl->mFlipRightSelectionHandleDirection = right;
1927 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1929 mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1932 void Decorator::ClearHighlights()
1934 mImpl->mHighlightQuadList.clear();
1935 mImpl->mHighlightPosition = Vector2::ZERO;
1938 void Decorator::SetHighlightColor( const Vector4& color )
1940 mImpl->mHighlightColor = color;
1943 const Vector4& Decorator::GetHighlightColor() const
1945 return mImpl->mHighlightColor;
1948 void Decorator::SetTextDepth( int textDepth )
1950 mImpl->mTextDepth = textDepth;
1953 void Decorator::SetPopupActive( bool active )
1955 mImpl->mActiveCopyPastePopup = active;
1958 bool Decorator::IsPopupActive() const
1960 return mImpl->mActiveCopyPastePopup ;
1963 void Decorator::SetEnabledPopupButtons( TextSelectionPopup::Buttons& enabledButtonsBitMask )
1965 mImpl->mEnabledPopupButtons = enabledButtonsBitMask;
1967 if ( !mImpl->mCopyPastePopup.actor )
1969 mImpl->mCopyPastePopup.actor = TextSelectionPopup::New( &mImpl->mTextSelectionPopupCallbackInterface );
1970 #ifdef DECORATOR_DEBUG
1971 mImpl->mCopyPastePopup.actor.SetName("mCopyPastePopup");
1973 mImpl->mCopyPastePopup.actor.SetAnchorPoint( AnchorPoint::CENTER );
1974 mImpl->mCopyPastePopup.actor.OnRelayoutSignal().Connect( mImpl, &Decorator::Impl::PopupRelayoutComplete ); // Position popup after size negotiation
1977 mImpl->mCopyPastePopup.actor.EnableButtons( mImpl->mEnabledPopupButtons );
1980 TextSelectionPopup::Buttons& Decorator::GetEnabledPopupButtons()
1982 return mImpl->mEnabledPopupButtons;
1987 void Decorator::SetScrollThreshold( float threshold )
1989 mImpl->SetScrollThreshold( threshold );
1992 float Decorator::GetScrollThreshold() const
1994 return mImpl->GetScrollThreshold();
1997 void Decorator::SetScrollSpeed( float speed )
1999 mImpl->SetScrollSpeed( speed );
2002 float Decorator::GetScrollSpeed() const
2004 return mImpl->GetScrollSpeed();
2007 void Decorator::NotifyEndOfScroll()
2009 mImpl->NotifyEndOfScroll();
2012 Decorator::~Decorator()
2017 Decorator::Decorator( ControllerInterface& controller,
2018 TextSelectionPopupCallbackInterface& callbackInterface )
2021 mImpl = new Decorator::Impl( controller, callbackInterface );
2026 } // namespace Toolkit