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 if( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] )
664 grabHandle.actor = ImageView::New( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] );
665 GetImpl( grabHandle.actor).SetDepthIndex( DepthIndex::DECORATION );
666 grabHandle.actor.SetAnchorPoint( AnchorPoint::TOP_CENTER );
668 // Area that Grab handle responds to, larger than actual handle so easier to move
669 #ifdef DECORATOR_DEBUG
670 grabHandle.actor.SetName( "GrabHandleActor" );
671 if ( Dali::Internal::gLogFilter->IsEnabledFor( Debug::Verbose ) )
673 grabHandle.grabArea = Control::New();
674 Toolkit::Control control = Toolkit::Control::DownCast( grabHandle.grabArea );
675 control.SetBackgroundColor( Vector4( 1.0f, 1.0f, 1.0f, 0.5f ) );
676 grabHandle.grabArea.SetName( "GrabArea" );
680 grabHandle.grabArea = Actor::New();
681 grabHandle.grabArea.SetName( "GrabArea" );
684 grabHandle.grabArea = Actor::New();
687 grabHandle.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
688 grabHandle.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
689 grabHandle.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
690 grabHandle.grabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
691 grabHandle.actor.Add( grabHandle.grabArea );
692 grabHandle.actor.SetColor( mHandleColor );
694 grabHandle.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnGrabHandleTouched );
695 mTapDetector.Attach( grabHandle.grabArea );
696 mPanGestureDetector.Attach( grabHandle.grabArea );
698 mActiveLayer.Add( grabHandle.actor );
702 if( grabHandle.actor && !grabHandle.actor.GetParent() )
704 mActiveLayer.Add( grabHandle.actor );
708 void CreateHandleMarker( HandleImpl& handle, Image& image, HandleType handleType )
712 handle.markerActor = ImageView::New( image );
713 handle.markerActor.SetColor( mHandleColor );
714 handle.actor.Add( handle.markerActor );
716 handle.markerActor.SetResizePolicy ( ResizePolicy::FIXED, Dimension::HEIGHT );
718 if( LEFT_SELECTION_HANDLE == handleType )
720 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_RIGHT );
721 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_RIGHT );
723 else if( RIGHT_SELECTION_HANDLE == handleType )
725 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_LEFT );
726 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
731 void CreateSelectionHandles()
733 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
736 if( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
738 primary.actor = ImageView::New( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
739 #ifdef DECORATOR_DEBUG
740 primary.actor.SetName("SelectionHandleOne");
742 primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
743 GetImpl( primary.actor ).SetDepthIndex( DepthIndex::DECORATION );
744 primary.actor.SetColor( mHandleColor );
746 primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
747 #ifdef DECORATOR_DEBUG
748 primary.grabArea.SetName("SelectionHandleOneGrabArea");
750 primary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
751 primary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
752 primary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
753 primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
755 mTapDetector.Attach( primary.grabArea );
756 mPanGestureDetector.Attach( primary.grabArea );
757 primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched );
759 primary.actor.Add( primary.grabArea );
761 CreateHandleMarker( primary, mHandleImages[LEFT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], LEFT_SELECTION_HANDLE );
765 if( primary.actor && !primary.actor.GetParent() )
767 mActiveLayer.Add( primary.actor );
770 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
771 if( !secondary.actor )
773 if( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
775 secondary.actor = ImageView::New( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
776 #ifdef DECORATOR_DEBUG
777 secondary.actor.SetName("SelectionHandleTwo");
779 secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text.
780 GetImpl( secondary.actor ).SetDepthIndex( DepthIndex::DECORATION );
781 secondary.actor.SetColor( mHandleColor );
783 secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
784 #ifdef DECORATOR_DEBUG
785 secondary.grabArea.SetName("SelectionHandleTwoGrabArea");
787 secondary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
788 secondary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
789 secondary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
790 secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
792 mTapDetector.Attach( secondary.grabArea );
793 mPanGestureDetector.Attach( secondary.grabArea );
794 secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched );
796 secondary.actor.Add( secondary.grabArea );
798 CreateHandleMarker( secondary, mHandleImages[RIGHT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], RIGHT_SELECTION_HANDLE );
802 if( secondary.actor && !secondary.actor.GetParent() )
804 mActiveLayer.Add( secondary.actor );
808 void CalculateHandleWorldCoordinates( HandleImpl& handle, Vector2& position )
810 // Gets the world position of the active layer. The active layer is where the handles are added.
811 const Vector3 parentWorldPosition = mActiveLayer.GetCurrentWorldPosition();
813 // The grab handle position in world coords.
814 // The active layer's world position is the center of the active layer. The origin of the
815 // coord system of the handles is the top left of the active layer.
816 position.x = parentWorldPosition.x - 0.5f * mControlSize.width + handle.position.x;
817 position.y = parentWorldPosition.y - 0.5f * mControlSize.height + handle.position.y;
820 void SetGrabHandlePosition()
822 // Reference to the grab handle.
823 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
825 // Transforms the handle position into world coordinates.
826 // @note This is not the same value as grabHandle.actor.GetCurrentWorldPosition()
827 // as it's transforming the handle's position set by the text-controller and not
828 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
829 // retrieves the position of the center of the actor but the handle's position set
830 // by the text controller is not the center of the actor.
831 Vector2 grabHandleWorldPosition;
832 CalculateHandleWorldCoordinates( grabHandle, grabHandleWorldPosition );
834 // Check if the grab handle exceeds the boundaries of the decoration box.
835 // At the moment only the height is checked for the grab handle.
837 grabHandle.verticallyFlipped = ( grabHandle.verticallyFlippedPreferred &&
838 ( ( grabHandleWorldPosition.y - grabHandle.size.height ) > mBoundingBox.y ) ) ||
839 ( grabHandleWorldPosition.y + grabHandle.lineHeight + grabHandle.size.height > mBoundingBox.w );
841 // The grab handle 'y' position in local coords.
842 // If the grab handle exceeds the bottom of the decoration box,
843 // set the 'y' position to the top of the line.
844 // The SetGrabHandleImage() method will change the orientation.
845 const float yLocalPosition = grabHandle.verticallyFlipped ? grabHandle.position.y : grabHandle.position.y + grabHandle.lineHeight;
847 if( grabHandle.actor )
849 grabHandle.actor.SetPosition( grabHandle.position.x + floor( 0.5f * mCursorWidth ),
850 yLocalPosition ); // TODO : Fix for multiline.
854 void SetSelectionHandlePosition( HandleType type )
856 const bool isPrimaryHandle = LEFT_SELECTION_HANDLE == type;
858 // Reference to the selection handle.
859 HandleImpl& handle = mHandle[type];
861 // Transforms the handle position into world coordinates.
862 // @note This is not the same value as handle.actor.GetCurrentWorldPosition()
863 // as it's transforming the handle's position set by the text-controller and not
864 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
865 // retrieves the position of the center of the actor but the handle's position set
866 // by the text controller is not the center of the actor.
867 Vector2 handleWorldPosition;
868 CalculateHandleWorldCoordinates( handle, handleWorldPosition );
870 // Whether to flip the handle (horizontally).
871 bool flipHandle = isPrimaryHandle ? mFlipLeftSelectionHandleDirection : mFlipRightSelectionHandleDirection;
873 // Whether to flip the handles if they are crossed.
874 bool crossFlip = false;
875 if( mFlipSelectionHandlesOnCross || !mHandlePanning )
877 crossFlip = mHandleCurrentCrossed;
880 // Does not flip if both conditions are true (double flip)
881 flipHandle = flipHandle != ( crossFlip || mHandlePreviousCrossed );
883 // Will flip the handles vertically if the user prefers it.
884 bool verticallyFlippedPreferred = handle.verticallyFlippedPreferred;
886 if( crossFlip || mHandlePreviousCrossed )
888 if( isPrimaryHandle )
890 verticallyFlippedPreferred = mHandle[RIGHT_SELECTION_HANDLE].verticallyFlippedPreferred;
894 verticallyFlippedPreferred = mHandle[LEFT_SELECTION_HANDLE].verticallyFlippedPreferred;
898 // Check if the selection handle exceeds the boundaries of the decoration box.
899 const bool exceedsLeftEdge = ( isPrimaryHandle ? !flipHandle : flipHandle ) && ( handleWorldPosition.x - handle.size.width < mBoundingBox.x );
900 const bool exceedsRightEdge = ( isPrimaryHandle ? flipHandle : !flipHandle ) && ( handleWorldPosition.x + handle.size.width > mBoundingBox.z );
902 // Does not flip if both conditions are true (double flip)
903 flipHandle = flipHandle != ( exceedsLeftEdge || exceedsRightEdge );
907 if( handle.actor && !handle.horizontallyFlipped )
909 // Change the anchor point to flip the image.
910 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_LEFT : AnchorPoint::TOP_RIGHT );
912 handle.horizontallyFlipped = true;
917 if( handle.actor && handle.horizontallyFlipped )
919 // Reset the anchor point.
920 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_RIGHT : AnchorPoint::TOP_LEFT );
922 handle.horizontallyFlipped = false;
926 // Whether to flip the handle vertically.
927 handle.verticallyFlipped = ( verticallyFlippedPreferred &&
928 ( ( handleWorldPosition.y - handle.size.height ) > mBoundingBox.y ) ) ||
929 ( handleWorldPosition.y + handle.lineHeight + handle.size.height > mBoundingBox.w );
931 // The primary selection handle 'y' position in local coords.
932 // If the handle exceeds the bottom of the decoration box,
933 // set the 'y' position to the top of the line.
934 // The SetHandleImage() method will change the orientation.
935 const float yLocalPosition = handle.verticallyFlipped ? handle.position.y : handle.position.y + handle.lineHeight;
939 handle.actor.SetPosition( handle.position.x,
940 yLocalPosition ); // TODO : Fix for multiline.
944 void SetHandleImage( HandleType type )
946 HandleImpl& handle = mHandle[type];
948 HandleType markerType = HANDLE_TYPE_COUNT;
949 // If the selection handle is flipped it chooses the image of the other selection handle. Does nothing for the grab handle.
950 if( LEFT_SELECTION_HANDLE == type )
952 type = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE;
953 markerType = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE_MARKER : LEFT_SELECTION_HANDLE_MARKER;
955 else if( RIGHT_SELECTION_HANDLE == type )
957 type = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE : RIGHT_SELECTION_HANDLE;
958 markerType = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE_MARKER : RIGHT_SELECTION_HANDLE_MARKER;
961 // Chooses between the released or pressed image. It checks whether the pressed image exists.
964 const HandleImageType imageType = ( handle.pressed ? ( mHandleImages[type][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
966 handle.actor.SetImage( mHandleImages[type][imageType] );
969 if( HANDLE_TYPE_COUNT != markerType )
971 if( handle.markerActor )
973 const HandleImageType markerImageType = ( handle.pressed ? ( mHandleImages[markerType][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
974 handle.markerActor.SetImage( mHandleImages[markerType][markerImageType] );
978 // Whether to flip the handle vertically.
981 handle.actor.SetOrientation( handle.verticallyFlipped ? ANGLE_180 : ANGLE_0, Vector3::XAXIS );
985 void CreateHighlight()
987 if( !mHighlightActor )
989 mHighlightActor = Actor::New();
991 #ifdef DECORATOR_DEBUG
992 mHighlightActor.SetName( "HighlightActor" );
994 mHighlightActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
995 mHighlightActor.SetSize( 1.0f, 1.0f );
996 mHighlightActor.SetColor( mHighlightColor );
997 mHighlightActor.SetColorMode( USE_OWN_COLOR );
1000 // Add the highlight box telling the controller it needs clipping.
1001 mController.AddDecoration( mHighlightActor, true );
1004 void UpdateHighlight()
1006 if ( mHighlightActor )
1008 if( !mHighlightQuadList.empty() )
1010 Vector< Vector2 > vertices;
1011 Vector< unsigned int> indices;
1014 std::vector<QuadCoordinates>::iterator iter = mHighlightQuadList.begin();
1015 std::vector<QuadCoordinates>::iterator endIter = mHighlightQuadList.end();
1017 for( std::size_t v = 0; iter != endIter; ++iter,v+=4 )
1019 QuadCoordinates& quad = *iter;
1022 vertex.x = quad.min.x;
1023 vertex.y = quad.min.y;
1024 vertices.PushBack( vertex );
1027 vertex.x = quad.max.x;
1028 vertex.y = quad.min.y;
1029 vertices.PushBack( vertex );
1031 // bottom-left (v+2)
1032 vertex.x = quad.min.x;
1033 vertex.y = quad.max.y;
1034 vertices.PushBack( vertex );
1036 // bottom-right (v+3)
1037 vertex.x = quad.max.x;
1038 vertex.y = quad.max.y;
1039 vertices.PushBack( vertex );
1041 // triangle A (3, 1, 0)
1042 indices.PushBack( v + 3 );
1043 indices.PushBack( v + 1 );
1044 indices.PushBack( v );
1046 // triangle B (0, 2, 3)
1047 indices.PushBack( v );
1048 indices.PushBack( v + 2 );
1049 indices.PushBack( v + 3 );
1054 mQuadVertices.SetSize( vertices.Size() );
1058 mQuadVertices = PropertyBuffer::New( mQuadVertexFormat, vertices.Size() );
1063 mQuadIndices.SetSize( indices.Size() );
1067 mQuadIndices = PropertyBuffer::New( mQuadIndexFormat, indices.Size() );
1070 mQuadVertices.SetData( &vertices[ 0 ] );
1071 mQuadIndices.SetData( &indices[ 0 ] );
1073 if( !mQuadGeometry )
1075 mQuadGeometry = Geometry::New();
1076 mQuadGeometry.AddVertexBuffer( mQuadVertices );
1078 mQuadGeometry.SetIndexBuffer( mQuadIndices );
1080 if( !mHighlightRenderer )
1082 mHighlightRenderer = Dali::Renderer::New( mQuadGeometry, mHighlightMaterial );
1083 mHighlightActor.AddRenderer( mHighlightRenderer );
1087 mHighlightActor.SetPosition( mHighlightPosition.x,
1088 mHighlightPosition.y );
1090 mHighlightQuadList.clear();
1092 if( mHighlightRenderer )
1094 mHighlightRenderer.SetProperty( Renderer::Property::DEPTH_INDEX, mTextDepth - 2 ); // text is rendered at mTextDepth and text's shadow at mTextDepth -1u.
1099 void OnTap( Actor actor, const TapGesture& tap )
1101 if( actor == mHandle[GRAB_HANDLE].actor )
1107 void DoPan( HandleImpl& handle, HandleType type, const PanGesture& gesture )
1109 if( Gesture::Started == gesture.state )
1111 handle.grabDisplacementX = handle.grabDisplacementY = 0;
1114 handle.grabDisplacementX += gesture.displacement.x;
1115 handle.grabDisplacementY += gesture.displacement.y;
1117 const float x = handle.position.x + handle.grabDisplacementX;
1118 const float y = handle.position.y + handle.lineHeight*0.5f + handle.grabDisplacementY;
1120 if( Gesture::Started == gesture.state ||
1121 Gesture::Continuing == gesture.state )
1124 mController.GetTargetSize( targetSize );
1126 if( x < mScrollThreshold )
1128 mScrollDirection = SCROLL_RIGHT;
1129 mHandleScrolling = type;
1132 else if( x > targetSize.width - mScrollThreshold )
1134 mScrollDirection = SCROLL_LEFT;
1135 mHandleScrolling = type;
1140 mHandleScrolling = HANDLE_TYPE_COUNT;
1142 mController.DecorationEvent( type, HANDLE_PRESSED, x, y );
1145 mHandlePanning = true;
1147 else if( Gesture::Finished == gesture.state ||
1148 Gesture::Cancelled == gesture.state )
1151 ( mScrollTimer.IsRunning() || mNotifyEndOfScroll ) )
1153 mNotifyEndOfScroll = false;
1154 mHandleScrolling = HANDLE_TYPE_COUNT;
1156 mController.DecorationEvent( type, HANDLE_STOP_SCROLLING, x, y );
1160 mController.DecorationEvent( type, HANDLE_RELEASED, x, y );
1165 handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_RELEASED] );
1167 handle.pressed = false;
1169 mHandlePanning = false;
1173 void OnPan( Actor actor, const PanGesture& gesture )
1175 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1176 HandleImpl& primarySelectionHandle = mHandle[LEFT_SELECTION_HANDLE];
1177 HandleImpl& secondarySelectionHandle = mHandle[RIGHT_SELECTION_HANDLE];
1179 if( actor == grabHandle.grabArea )
1181 DoPan( grabHandle, GRAB_HANDLE, gesture );
1183 else if( actor == primarySelectionHandle.grabArea )
1185 DoPan( primarySelectionHandle, LEFT_SELECTION_HANDLE, gesture );
1187 else if( actor == secondarySelectionHandle.grabArea )
1189 DoPan( secondarySelectionHandle, RIGHT_SELECTION_HANDLE, gesture );
1193 bool OnGrabHandleTouched( Actor actor, const TouchEvent& event )
1195 // Switch between pressed/release grab-handle images
1196 if( event.GetPointCount() > 0 &&
1197 mHandle[GRAB_HANDLE].actor )
1199 const TouchPoint& point = event.GetPoint(0);
1201 if( TouchPoint::Down == point.state )
1203 mHandle[GRAB_HANDLE].pressed = true;
1205 else if( ( TouchPoint::Up == point.state ) ||
1206 ( TouchPoint::Interrupted == point.state ) )
1208 mHandle[GRAB_HANDLE].pressed = false;
1211 SetHandleImage( GRAB_HANDLE );
1214 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1218 bool OnHandleOneTouched( Actor actor, const TouchEvent& event )
1220 // Switch between pressed/release selection handle images
1221 if( event.GetPointCount() > 0 &&
1222 mHandle[LEFT_SELECTION_HANDLE].actor )
1224 const TouchPoint& point = event.GetPoint(0);
1226 if( TouchPoint::Down == point.state )
1228 mHandle[LEFT_SELECTION_HANDLE].pressed = true;
1230 else if( ( TouchPoint::Up == point.state ) ||
1231 ( TouchPoint::Interrupted == point.state ) )
1233 mHandle[LEFT_SELECTION_HANDLE].pressed = false;
1234 mHandlePreviousCrossed = mHandleCurrentCrossed;
1235 mHandlePanning = false;
1238 SetHandleImage( LEFT_SELECTION_HANDLE );
1241 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1245 bool OnHandleTwoTouched( Actor actor, const TouchEvent& event )
1247 // Switch between pressed/release selection handle images
1248 if( event.GetPointCount() > 0 &&
1249 mHandle[RIGHT_SELECTION_HANDLE].actor )
1251 const TouchPoint& point = event.GetPoint(0);
1253 if( TouchPoint::Down == point.state )
1255 mHandle[RIGHT_SELECTION_HANDLE].pressed = true;
1257 else if( ( TouchPoint::Up == point.state ) ||
1258 ( TouchPoint::Interrupted == point.state ) )
1260 mHandle[RIGHT_SELECTION_HANDLE].pressed = false;
1261 mHandlePreviousCrossed = mHandleCurrentCrossed;
1262 mHandlePanning = false;
1265 SetHandleImage( RIGHT_SELECTION_HANDLE );
1268 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1272 void HandleResetPosition( PropertyNotification& source )
1274 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1276 if( grabHandle.active )
1278 // Sets the grab handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1279 SetGrabHandlePosition();
1281 // Sets the grab handle image according if it's pressed, flipped, etc.
1282 SetHandleImage( GRAB_HANDLE );
1286 // Sets the primary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1287 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
1289 // Sets the primary handle image according if it's pressed, flipped, etc.
1290 SetHandleImage( LEFT_SELECTION_HANDLE );
1292 // Sets the secondary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1293 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
1295 // Sets the secondary handle image according if it's pressed, flipped, etc.
1296 SetHandleImage( RIGHT_SELECTION_HANDLE );
1300 void SetupActiveLayerPropertyNotifications()
1307 // Vertical notifications.
1309 // Disconnect any previous connected callback.
1310 if( mVerticalLessThanNotification )
1312 mVerticalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1313 mActiveLayer.RemovePropertyNotification( mVerticalLessThanNotification );
1316 if( mVerticalGreaterThanNotification )
1318 mVerticalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1319 mActiveLayer.RemovePropertyNotification( mVerticalGreaterThanNotification );
1322 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1323 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1324 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1326 if( grabHandle.active )
1328 if( grabHandle.verticallyFlipped )
1330 // The grab handle is vertically flipped. Never is going to exceed the bottom edje of the display.
1331 mVerticalGreaterThanNotification.Reset();
1333 // The vertical distance from the center of the active layer to the top edje of the display.
1334 const float topHeight = 0.5f * mControlSize.height - grabHandle.position.y + grabHandle.size.height;
1336 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1337 LessThanCondition( mBoundingBox.y + topHeight ) );
1339 // Notifies the change from false to true and from true to false.
1340 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1342 // Connects the signals with the callbacks.
1343 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1347 // The grab handle is not vertically flipped. Never is going to exceed the top edje of the display.
1348 mVerticalLessThanNotification.Reset();
1350 // The vertical distance from the center of the active layer to the bottom edje of the display.
1351 const float bottomHeight = -0.5f * mControlSize.height + grabHandle.position.y + grabHandle.lineHeight + grabHandle.size.height;
1353 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1354 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1356 // Notifies the change from false to true and from true to false.
1357 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1359 // Connects the signals with the callbacks.
1360 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1363 else // The selection handles are active
1365 if( primaryHandle.verticallyFlipped && secondaryHandle.verticallyFlipped )
1367 // Both selection handles are vertically flipped. Never are going to exceed the bottom edje of the display.
1368 mVerticalGreaterThanNotification.Reset();
1370 // The vertical distance from the center of the active layer to the top edje of the display.
1371 const float topHeight = 0.5f * mControlSize.height + std::max( -primaryHandle.position.y + primaryHandle.size.height, -secondaryHandle.position.y + secondaryHandle.size.height );
1373 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1374 LessThanCondition( mBoundingBox.y + topHeight ) );
1376 // Notifies the change from false to true and from true to false.
1377 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1379 // Connects the signals with the callbacks.
1380 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1382 else if( !primaryHandle.verticallyFlipped && !secondaryHandle.verticallyFlipped )
1384 // Both selection handles aren't vertically flipped. Never are going to exceed the top edje of the display.
1385 mVerticalLessThanNotification.Reset();
1387 // The vertical distance from the center of the active layer to the bottom edje of the display.
1388 const float bottomHeight = -0.5f * mControlSize.height + std::max( primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height,
1389 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height );
1391 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1392 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1394 // Notifies the change from false to true and from true to false.
1395 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1397 // Connects the signals with the callbacks.
1398 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1402 // Only one of the selection handles is vertically flipped. Both vertical notifications are needed.
1404 // The vertical distance from the center of the active layer to the top edje of the display.
1405 const float topHeight = 0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1406 -primaryHandle.position.y + primaryHandle.size.height :
1407 -secondaryHandle.position.y + secondaryHandle.size.height );
1409 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1410 LessThanCondition( mBoundingBox.y + topHeight ) );
1412 // Notifies the change from false to true and from true to false.
1413 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1415 // Connects the signals with the callbacks.
1416 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1418 // The vertical distance from the center of the active layer to the bottom edje of the display.
1419 const float bottomHeight = -0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1420 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height :
1421 primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height );
1423 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1424 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1426 // Notifies the change from false to true and from true to false.
1427 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1429 // Connects the signals with the callbacks.
1430 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1434 // Horizontal notifications.
1436 // Disconnect any previous connected callback.
1437 if( mHorizontalLessThanNotification )
1439 mHorizontalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1440 mActiveLayer.RemovePropertyNotification( mHorizontalLessThanNotification );
1443 if( mHorizontalGreaterThanNotification )
1445 mHorizontalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1446 mActiveLayer.RemovePropertyNotification( mHorizontalGreaterThanNotification );
1449 if( primaryHandle.active || secondaryHandle.active )
1451 // The horizontal distance from the center of the active layer to the left edje of the display.
1452 const float leftWidth = 0.5f * mControlSize.width + std::max( -primaryHandle.position.x + primaryHandle.size.width,
1453 -secondaryHandle.position.x + secondaryHandle.size.width );
1455 mHorizontalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1456 LessThanCondition( mBoundingBox.x + leftWidth ) );
1458 // Notifies the change from false to true and from true to false.
1459 mHorizontalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1461 // Connects the signals with the callbacks.
1462 mHorizontalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1464 // The horizontal distance from the center of the active layer to the right edje of the display.
1465 const float rightWidth = -0.5f * mControlSize.width + std::max( primaryHandle.position.x + primaryHandle.size.width,
1466 secondaryHandle.position.x + secondaryHandle.size.width );
1468 mHorizontalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1469 GreaterThanCondition( mBoundingBox.z - rightWidth ) );
1471 // Notifies the change from false to true and from true to false.
1472 mHorizontalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1474 // Connects the signals with the callbacks.
1475 mHorizontalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1481 float AlternatePopUpPositionRelativeToCursor()
1483 float alternativePosition = 0.0f;
1485 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1487 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1488 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1489 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1490 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
1492 if( primaryHandle.active || secondaryHandle.active )
1494 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
1495 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
1499 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + grabHandle.size.height + cursor.position.y;
1502 return alternativePosition;
1505 void PopUpLeavesVerticalBoundary( PropertyNotification& source )
1507 float alternativeYPosition = 0.0f;
1508 // todo use AlternatePopUpPositionRelativeToSelectionHandles() if text is highlighted
1509 // if can't be positioned above, then position below row.
1510 alternativeYPosition = AlternatePopUpPositionRelativeToCursor();
1512 mCopyPastePopup.actor.SetY( alternativeYPosition );
1515 void SetUpPopupPositionNotifications()
1517 // Note Property notifications ignore any set anchor point so conditions must allow for this. Default is Top Left.
1519 // Exceeding vertical boundary
1521 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1523 PropertyNotification verticalExceedNotification = mCopyPastePopup.actor.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1524 OutsideCondition( mBoundingBox.y + popupHeight * 0.5f,
1525 mBoundingBox.w - popupHeight * 0.5f ) );
1527 verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary );
1530 void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, const Vector3& popupDistanceFromAnchorPoint, Actor parent, const Vector4& boundingRectangleWorld )
1532 DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() )
1534 // Parent must already by added to Stage for these Get calls to work
1535 const Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize() * parent.GetCurrentAnchorPoint();
1536 const Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition; // Parent World position plus popup local position gives World Position
1538 // Calculate distance to move popup (in local space) so fits within the boundary
1539 float xOffSetToKeepWithinBounds = 0.0f;
1540 if( popupWorldPosition.x - popupDistanceFromAnchorPoint.x < boundingRectangleWorld.x )
1542 xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x );
1544 else if( popupWorldPosition.x + popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z )
1546 xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x + popupDistanceFromAnchorPoint.x );
1549 // Ensure initial display of Popup is in alternative position if can not fit above. As Property notification will be a frame behind.
1550 if( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y )
1552 requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor();
1555 requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds;
1557 // Prevent pixel mis-alignment by rounding down.
1558 requiredPopupPosition.x = floor( requiredPopupPosition.x );
1559 requiredPopupPosition.y = floor( requiredPopupPosition.y );
1562 void SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1564 HandleImpl& handle = mHandle[handleType];
1565 handle.size = Size( image.GetWidth(), image.GetHeight() );
1567 mHandleImages[handleType][handleImageType] = image;
1570 void SetScrollThreshold( float threshold )
1572 mScrollThreshold = threshold;
1575 float GetScrollThreshold() const
1577 return mScrollThreshold;
1580 void SetScrollSpeed( float speed )
1582 mScrollSpeed = speed;
1583 mScrollDistance = speed * SCROLL_TICK_INTERVAL * TO_SECONDS;
1586 float GetScrollSpeed() const
1588 return mScrollSpeed;
1591 void NotifyEndOfScroll()
1597 mNotifyEndOfScroll = true;
1602 * Creates and starts a timer to scroll the text when handles are close to the edges of the text.
1604 * It only starts the timer if it's already created.
1606 void StartScrollTimer()
1610 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1611 mScrollTimer.TickSignal().Connect( this, &Decorator::Impl::OnScrollTimerTick );
1614 if( !mScrollTimer.IsRunning() )
1616 mScrollTimer.Start();
1621 * Stops the timer used to scroll the text.
1623 void StopScrollTimer()
1627 mScrollTimer.Stop();
1632 * Callback called by the timer used to scroll the text.
1634 * It calculates and sets a new scroll position.
1636 bool OnScrollTimerTick()
1638 if( HANDLE_TYPE_COUNT != mHandleScrolling )
1640 mController.DecorationEvent( mHandleScrolling,
1642 mScrollDirection == SCROLL_RIGHT ? mScrollDistance : -mScrollDistance,
1649 ControllerInterface& mController;
1651 TapGestureDetector mTapDetector;
1652 PanGestureDetector mPanGestureDetector;
1653 Timer mCursorBlinkTimer; ///< Timer to signal cursor to blink
1654 Timer mScrollTimer; ///< Timer used to scroll the text when the grab handle is moved close to the edges.
1656 Layer mActiveLayer; ///< Layer for active handles and alike that ensures they are above all else.
1657 PropertyNotification mVerticalLessThanNotification; ///< Notifies when the 'y' coord of the active layer is less than a given value.
1658 PropertyNotification mVerticalGreaterThanNotification; ///< Notifies when the 'y' coord of the active layer is grater than a given value.
1659 PropertyNotification mHorizontalLessThanNotification; ///< Notifies when the 'x' coord of the active layer is less than a given value.
1660 PropertyNotification mHorizontalGreaterThanNotification; ///< Notifies when the 'x' coord of the active layer is grater than a given value.
1661 Control mPrimaryCursor;
1662 Control mSecondaryCursor;
1664 Actor mHighlightActor; ///< Actor to display highlight
1665 Renderer mHighlightRenderer;
1666 Material mHighlightMaterial; ///< Material used for highlight
1667 Property::Map mQuadVertexFormat;
1668 Property::Map mQuadIndexFormat;
1669 PopupImpl mCopyPastePopup;
1670 TextSelectionPopup::Buttons mEnabledPopupButtons; /// Bit mask of currently enabled Popup buttons
1671 TextSelectionPopupCallbackInterface& mTextSelectionPopupCallbackInterface;
1673 Image mHandleImages[HANDLE_TYPE_COUNT][HANDLE_IMAGE_TYPE_COUNT];
1674 Vector4 mHandleColor;
1676 CursorImpl mCursor[CURSOR_COUNT];
1677 HandleImpl mHandle[HANDLE_TYPE_COUNT];
1679 PropertyBuffer mQuadVertices;
1680 PropertyBuffer mQuadIndices;
1681 Geometry mQuadGeometry;
1682 QuadContainer mHighlightQuadList; ///< Sub-selections that combine to create the complete selection highlight
1684 Vector4 mBoundingBox; ///< The bounding box in world coords.
1685 Vector4 mHighlightColor; ///< Color of the highlight
1686 Vector2 mHighlightPosition; ///< The position of the highlight actor.
1687 Vector2 mControlSize; ///< The control's size. Set by the Relayout.
1689 unsigned int mActiveCursor;
1690 unsigned int mCursorBlinkInterval;
1691 float mCursorBlinkDuration;
1692 float mCursorWidth; ///< The width of the cursors in pixels.
1693 HandleType mHandleScrolling; ///< The handle which is scrolling.
1694 ScrollDirection mScrollDirection; ///< The direction of the scroll.
1695 float mScrollThreshold; ///< Defines a square area inside the control, close to the edge. A cursor entering this area will trigger scroll events.
1696 float mScrollSpeed; ///< The scroll speed in pixels per second.
1697 float mScrollDistance; ///< Distance the text scrolls during a scroll interval.
1698 int mTextDepth; ///< The depth used to render the text.
1700 bool mActiveCopyPastePopup : 1;
1701 bool mPopupSetNewPosition : 1;
1702 bool mCursorBlinkStatus : 1; ///< Flag to switch between blink on and blink off.
1703 bool mDelayCursorBlink : 1; ///< Used to avoid cursor blinking when entering text.
1704 bool mPrimaryCursorVisible : 1; ///< Whether the primary cursor is visible.
1705 bool mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible.
1706 bool mFlipSelectionHandlesOnCross : 1; ///< Whether to flip the selection handles as soon as they cross.
1707 bool mFlipLeftSelectionHandleDirection : 1; ///< Whether to flip the left selection handle image because of the character's direction.
1708 bool mFlipRightSelectionHandleDirection : 1; ///< Whether to flip the right selection handle image because of the character's direction.
1709 bool mHandlePanning : 1; ///< Whether any of the handles is moving.
1710 bool mHandleCurrentCrossed : 1; ///< Whether the handles are crossed.
1711 bool mHandlePreviousCrossed : 1; ///< Whether the handles where crossed at the last handle touch up.
1712 bool mNotifyEndOfScroll : 1; ///< Whether to notify the end of the scroll.
1715 DecoratorPtr Decorator::New( ControllerInterface& controller,
1716 TextSelectionPopupCallbackInterface& callbackInterface )
1718 return DecoratorPtr( new Decorator( controller,
1719 callbackInterface ) );
1722 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
1724 LocalToWorldCoordinatesBoundingBox( boundingBox, mImpl->mBoundingBox );
1727 void Decorator::GetBoundingBox( Rect<int>& boundingBox ) const
1729 WorldToLocalCoordinatesBoundingBox( mImpl->mBoundingBox, boundingBox );
1732 void Decorator::Relayout( const Vector2& size )
1734 mImpl->Relayout( size );
1737 void Decorator::UpdatePositions( const Vector2& scrollOffset )
1739 mImpl->UpdatePositions( scrollOffset );
1744 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
1746 mImpl->mActiveCursor = activeCursor;
1749 unsigned int Decorator::GetActiveCursor() const
1751 return mImpl->mActiveCursor;
1754 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
1756 mImpl->mCursor[cursor].position.x = x;
1757 mImpl->mCursor[cursor].position.y = y;
1758 mImpl->mCursor[cursor].cursorHeight = cursorHeight;
1759 mImpl->mCursor[cursor].lineHeight = lineHeight;
1762 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
1764 x = mImpl->mCursor[cursor].position.x;
1765 y = mImpl->mCursor[cursor].position.y;
1766 cursorHeight = mImpl->mCursor[cursor].cursorHeight;
1767 lineHeight = mImpl->mCursor[cursor].lineHeight;
1770 const Vector2& Decorator::GetPosition( Cursor cursor ) const
1772 return mImpl->mCursor[cursor].position;
1775 void Decorator::SetCursorColor( Cursor cursor, const Dali::Vector4& color )
1777 mImpl->mCursor[cursor].color = color;
1780 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
1782 return mImpl->mCursor[cursor].color;
1785 void Decorator::StartCursorBlink()
1787 if ( !mImpl->mCursorBlinkTimer )
1789 mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
1790 mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
1793 if ( !mImpl->mCursorBlinkTimer.IsRunning() )
1795 mImpl->mCursorBlinkTimer.Start();
1799 void Decorator::StopCursorBlink()
1801 if ( mImpl->mCursorBlinkTimer )
1803 mImpl->mCursorBlinkTimer.Stop();
1806 mImpl->mCursorBlinkStatus = true; // Keep cursor permanently shown
1809 void Decorator::DelayCursorBlink()
1811 mImpl->mCursorBlinkStatus = true; // Show cursor for a bit longer
1812 mImpl->mDelayCursorBlink = true;
1815 void Decorator::SetCursorBlinkInterval( float seconds )
1817 mImpl->mCursorBlinkInterval = static_cast<unsigned int>( seconds * TO_MILLISECONDS ); // Convert to milliseconds
1820 float Decorator::GetCursorBlinkInterval() const
1822 return static_cast<float>( mImpl->mCursorBlinkInterval ) * TO_SECONDS;
1825 void Decorator::SetCursorBlinkDuration( float seconds )
1827 mImpl->mCursorBlinkDuration = seconds;
1830 float Decorator::GetCursorBlinkDuration() const
1832 return mImpl->mCursorBlinkDuration;
1835 void Decorator::SetCursorWidth( int width )
1837 mImpl->mCursorWidth = static_cast<float>( width );
1840 int Decorator::GetCursorWidth() const
1842 return static_cast<int>( mImpl->mCursorWidth );
1847 void Decorator::SetHandleActive( HandleType handleType, bool active )
1849 mImpl->mHandle[handleType].active = active;
1853 if( ( LEFT_SELECTION_HANDLE == handleType ) || ( RIGHT_SELECTION_HANDLE == handleType ) )
1855 mImpl->mHandlePreviousCrossed = false;
1858 // TODO: this is a work-around.
1859 // The problem is the handle actor does not receive the touch event with the Interrupt
1860 // state when the power button is pressed and the application goes to background.
1861 mImpl->mHandle[handleType].pressed = false;
1862 Image imageReleased = mImpl->mHandleImages[handleType][HANDLE_IMAGE_RELEASED];
1863 ImageView imageView = mImpl->mHandle[handleType].actor;
1864 if( imageReleased && imageView )
1866 imageView.SetImage( imageReleased );
1872 bool Decorator::IsHandleActive( HandleType handleType ) const
1874 return mImpl->mHandle[handleType].active ;
1877 void Decorator::SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1879 mImpl->SetHandleImage( handleType, handleImageType, image );
1882 Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType handleImageType ) const
1884 return mImpl->mHandleImages[handleType][handleImageType];
1887 void Decorator::SetHandleColor( const Vector4& color )
1889 mImpl->mHandleColor = color;
1892 const Vector4& Decorator::GetHandleColor() const
1894 return mImpl->mHandleColor;
1897 void Decorator::SetPosition( HandleType handleType, float x, float y, float height )
1899 // Adjust handle's displacement
1900 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1902 handle.grabDisplacementX -= x - handle.position.x;
1903 handle.grabDisplacementY -= y - handle.position.y;
1905 handle.position.x = x;
1906 handle.position.y = y;
1907 handle.lineHeight = height;
1910 void Decorator::GetPosition( HandleType handleType, float& x, float& y, float& height ) const
1912 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1914 x = handle.position.x;
1915 y = handle.position.y;
1916 height = handle.lineHeight;
1919 const Vector2& Decorator::GetPosition( HandleType handleType ) const
1921 return mImpl->mHandle[handleType].position;
1924 void Decorator::FlipHandleVertically( HandleType handleType, bool flip )
1926 mImpl->mHandle[handleType].verticallyFlippedPreferred = flip;
1929 bool Decorator::IsHandleVerticallyFlipped( HandleType handleType ) const
1931 return mImpl->mHandle[handleType].verticallyFlippedPreferred;
1934 void Decorator::FlipSelectionHandlesOnCrossEnabled( bool enable )
1936 mImpl->mFlipSelectionHandlesOnCross = enable;
1939 void Decorator::SetSelectionHandleFlipState( bool indicesSwapped, bool left, bool right )
1941 mImpl->mHandleCurrentCrossed = indicesSwapped;
1942 mImpl->mFlipLeftSelectionHandleDirection = left;
1943 mImpl->mFlipRightSelectionHandleDirection = right;
1946 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1948 mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1951 void Decorator::ClearHighlights()
1953 mImpl->mHighlightQuadList.clear();
1954 mImpl->mHighlightPosition = Vector2::ZERO;
1957 void Decorator::SetHighlightColor( const Vector4& color )
1959 mImpl->mHighlightColor = color;
1962 const Vector4& Decorator::GetHighlightColor() const
1964 return mImpl->mHighlightColor;
1967 void Decorator::SetTextDepth( int textDepth )
1969 mImpl->mTextDepth = textDepth;
1972 void Decorator::SetPopupActive( bool active )
1974 mImpl->mActiveCopyPastePopup = active;
1977 bool Decorator::IsPopupActive() const
1979 return mImpl->mActiveCopyPastePopup ;
1982 void Decorator::SetEnabledPopupButtons( TextSelectionPopup::Buttons& enabledButtonsBitMask )
1984 mImpl->mEnabledPopupButtons = enabledButtonsBitMask;
1986 if ( !mImpl->mCopyPastePopup.actor )
1988 mImpl->mCopyPastePopup.actor = TextSelectionPopup::New( &mImpl->mTextSelectionPopupCallbackInterface );
1989 #ifdef DECORATOR_DEBUG
1990 mImpl->mCopyPastePopup.actor.SetName("mCopyPastePopup");
1992 mImpl->mCopyPastePopup.actor.SetAnchorPoint( AnchorPoint::CENTER );
1993 mImpl->mCopyPastePopup.actor.OnRelayoutSignal().Connect( mImpl, &Decorator::Impl::PopupRelayoutComplete ); // Position popup after size negotiation
1996 mImpl->mCopyPastePopup.actor.EnableButtons( mImpl->mEnabledPopupButtons );
1999 TextSelectionPopup::Buttons& Decorator::GetEnabledPopupButtons()
2001 return mImpl->mEnabledPopupButtons;
2006 void Decorator::SetScrollThreshold( float threshold )
2008 mImpl->SetScrollThreshold( threshold );
2011 float Decorator::GetScrollThreshold() const
2013 return mImpl->GetScrollThreshold();
2016 void Decorator::SetScrollSpeed( float speed )
2018 mImpl->SetScrollSpeed( speed );
2021 float Decorator::GetScrollSpeed() const
2023 return mImpl->GetScrollSpeed();
2026 void Decorator::NotifyEndOfScroll()
2028 mImpl->NotifyEndOfScroll();
2031 Decorator::~Decorator()
2036 Decorator::Decorator( ControllerInterface& controller,
2037 TextSelectionPopupCallbackInterface& callbackInterface )
2040 mImpl = new Decorator::Impl( controller, callbackInterface );
2045 } // namespace Toolkit