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/text-controller-impl.h>
22 #include <dali/public-api/adaptor-framework/key.h>
23 #include <dali/integration-api/debug.h>
26 #include <dali-toolkit/internal/text/bidirectional-support.h>
27 #include <dali-toolkit/internal/text/character-set-conversion.h>
28 #include <dali-toolkit/internal/text/multi-language-support.h>
29 #include <dali-toolkit/internal/text/segmentation.h>
30 #include <dali-toolkit/internal/text/shaper.h>
35 #if defined(DEBUG_ENABLED)
36 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
40 * @brief Some characters can be shaped in more than one glyph.
41 * This struct is used to retrieve metrics from these group of glyphs.
55 float fontHeight; ///< The font's height of that glyphs.
56 float advance; ///< The sum of all the advances of all the glyphs.
57 float ascender; ///< The font's ascender.
58 float xBearing; ///< The x bearing of the first glyph.
73 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
75 * @param[in] glyphIndex The index to the first glyph.
76 * @param[in] numberOfGlyphs The number of glyphs.
77 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
78 * @param[in] visualModel The visual model.
79 * @param[in] metrics Used to access metrics from FontClient.
81 void GetGlyphsMetrics( GlyphIndex glyphIndex,
82 Length numberOfGlyphs,
83 GlyphMetrics& glyphMetrics,
84 VisualModelPtr& visualModel,
87 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
89 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
91 Text::FontMetrics fontMetrics;
92 metrics->GetFontMetrics( firstGlyph.fontId, fontMetrics );
94 glyphMetrics.fontHeight = fontMetrics.height;
95 glyphMetrics.advance = firstGlyph.advance;
96 glyphMetrics.ascender = fontMetrics.ascender;
97 glyphMetrics.xBearing = firstGlyph.xBearing;
99 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
101 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
103 glyphMetrics.advance += glyphInfo.advance;
107 EventData::EventData( DecoratorPtr decorator )
108 : mDecorator( decorator ),
110 mPlaceholderTextActive(),
111 mPlaceholderTextInactive(),
112 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
116 mPrimaryCursorPosition( 0u ),
117 mLeftSelectionPosition( 0u ),
118 mRightSelectionPosition( 0u ),
119 mPreEditStartPosition( 0u ),
120 mPreEditLength( 0u ),
121 mIsShowingPlaceholderText( false ),
122 mPreEditFlag( false ),
123 mDecoratorUpdated( false ),
124 mCursorBlinkEnabled( true ),
125 mGrabHandleEnabled( true ),
126 mGrabHandlePopupEnabled( true ),
127 mSelectionEnabled( true ),
128 mHorizontalScrollingEnabled( true ),
129 mVerticalScrollingEnabled( false ),
130 mUpdateCursorPosition( false ),
131 mUpdateLeftSelectionPosition( false ),
132 mUpdateRightSelectionPosition( false ),
133 mScrollAfterUpdatePosition( false ),
134 mScrollAfterDelete( false ),
135 mAllTextSelected( false )
137 mImfManager = ImfManager::Get();
140 EventData::~EventData()
143 bool Controller::Impl::ProcessInputEvents()
145 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
146 if( NULL == mEventData )
148 // Nothing to do if there is no text input.
149 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
153 if( mEventData->mDecorator )
155 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
156 iter != mEventData->mEventQueue.end();
161 case Event::CURSOR_KEY_EVENT:
163 OnCursorKeyEvent( *iter );
166 case Event::TAP_EVENT:
171 case Event::LONG_PRESS_EVENT:
173 OnLongPressEvent( *iter );
176 case Event::PAN_EVENT:
181 case Event::GRAB_HANDLE_EVENT:
182 case Event::LEFT_SELECTION_HANDLE_EVENT:
183 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
185 OnHandleEvent( *iter );
190 OnSelectEvent( *iter );
193 case Event::SELECT_ALL:
202 // The cursor must also be repositioned after inserts into the model
203 if( mEventData->mUpdateCursorPosition )
205 // Updates the cursor position and scrolls the text to make it visible.
206 CursorInfo cursorInfo;
207 GetCursorPosition( mEventData->mPrimaryCursorPosition,
210 if( mEventData->mScrollAfterUpdatePosition )
212 ScrollToMakePositionVisible( cursorInfo.primaryPosition );
213 mEventData->mScrollAfterUpdatePosition = false;
215 else if( mEventData->mScrollAfterDelete )
217 ScrollTextToMatchCursor( cursorInfo );
218 mEventData->mScrollAfterDelete = false;
221 UpdateCursorPosition( cursorInfo );
223 mEventData->mDecoratorUpdated = true;
224 mEventData->mUpdateCursorPosition = false;
228 bool leftScroll = false;
229 bool rightScroll = false;
231 CursorInfo leftHandleInfo;
232 CursorInfo rightHandleInfo;
234 if( mEventData->mUpdateLeftSelectionPosition )
236 GetCursorPosition( mEventData->mLeftSelectionPosition,
239 if( mEventData->mScrollAfterUpdatePosition )
241 ScrollToMakePositionVisible( leftHandleInfo.primaryPosition );
246 if( mEventData->mUpdateRightSelectionPosition )
248 GetCursorPosition( mEventData->mRightSelectionPosition,
251 if( mEventData->mScrollAfterUpdatePosition )
253 ScrollToMakePositionVisible( rightHandleInfo.primaryPosition );
258 if( mEventData->mUpdateLeftSelectionPosition )
260 UpdateSelectionHandle( LEFT_SELECTION_HANDLE,
264 mEventData->mDecoratorUpdated = true;
267 if( mEventData->mUpdateRightSelectionPosition )
269 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE,
273 mEventData->mDecoratorUpdated = true;
276 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
278 RepositionSelectionHandles();
280 mEventData->mUpdateLeftSelectionPosition = false;
281 mEventData->mUpdateRightSelectionPosition = false;
284 if( leftScroll || rightScroll )
286 mEventData->mScrollAfterUpdatePosition = false;
290 mEventData->mEventQueue.clear();
292 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
294 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
295 mEventData->mDecoratorUpdated = false;
297 return decoratorUpdated;
300 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
302 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::UpdateModel\n" );
304 // Calculate the operations to be done.
305 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
307 Vector<Character>& utf32Characters = mLogicalModel->mText;
309 const Length numberOfCharacters = utf32Characters.Count();
311 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
312 if( GET_LINE_BREAKS & operations )
314 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
315 // calculate the bidirectional info for each 'paragraph'.
316 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
317 // is not shaped together).
318 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
320 SetLineBreakInfo( utf32Characters,
324 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
325 if( GET_WORD_BREAKS & operations )
327 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
328 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
330 SetWordBreakInfo( utf32Characters,
334 const bool getScripts = GET_SCRIPTS & operations;
335 const bool validateFonts = VALIDATE_FONTS & operations;
337 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
338 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
340 if( getScripts || validateFonts )
342 // Validates the fonts assigned by the application or assigns default ones.
343 // It makes sure all the characters are going to be rendered by the correct font.
344 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
348 // Retrieves the scripts used in the text.
349 multilanguageSupport.SetScripts( utf32Characters,
355 if( 0u == validFonts.Count() )
357 // Copy the requested font defaults received via the property system.
358 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
359 GetDefaultFonts( validFonts, numberOfCharacters );
362 // Validates the fonts. If there is a character with no assigned font it sets a default one.
363 // After this call, fonts are validated.
364 multilanguageSupport.ValidateFonts( utf32Characters,
370 Vector<Character> mirroredUtf32Characters;
371 bool textMirrored = false;
372 Length numberOfParagraphs = 0u;
373 if( BIDI_INFO & operations )
375 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
376 // bidirectional info.
378 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
379 for( Length index = 0u; index < numberOfCharacters; ++index )
381 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
383 ++numberOfParagraphs;
387 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
388 bidirectionalInfo.Reserve( numberOfParagraphs );
390 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
391 SetBidirectionalInfo( utf32Characters,
396 if( 0u != bidirectionalInfo.Count() )
398 // This paragraph has right to left text. Some characters may need to be mirrored.
399 // TODO: consider if the mirrored string can be stored as well.
401 textMirrored = GetMirroredText( utf32Characters,
402 mirroredUtf32Characters,
405 // Only set the character directions if there is right to left characters.
406 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
407 directions.Resize( numberOfCharacters );
409 GetCharactersDirection( bidirectionalInfo,
414 // There is no right to left characters. Clear the directions vector.
415 mLogicalModel->mCharacterDirections.Clear();
419 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
420 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
421 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
422 Vector<GlyphIndex> newParagraphGlyphs;
423 newParagraphGlyphs.Reserve( numberOfParagraphs );
425 if( SHAPE_TEXT & operations )
427 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
429 ShapeText( textToShape,
434 glyphsToCharactersMap,
436 newParagraphGlyphs );
438 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
439 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
440 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
443 const Length numberOfGlyphs = glyphs.Count();
445 if( GET_GLYPH_METRICS & operations )
447 GlyphInfo* glyphsBuffer = glyphs.Begin();
448 mMetrics->GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
450 // Update the width and advance of all new paragraph characters.
451 for( Vector<GlyphIndex>::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it )
453 const GlyphIndex index = *it;
454 GlyphInfo& glyph = *( glyphsBuffer + index );
456 glyph.xBearing = 0.f;
463 mEventData->mPreEditFlag &&
464 ( 0u != mVisualModel->mCharactersToGlyph.Count() ) )
466 // Add the underline for the pre-edit text.
467 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
468 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
470 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition );
471 const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u );
472 const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter );
473 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u );
475 GlyphRun underlineRun;
476 underlineRun.glyphIndex = glyphStart;
477 underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart;
479 // TODO: At the moment the underline runs are only for pre-edit.
480 mVisualModel->mUnderlineRuns.PushBack( underlineRun );
484 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
488 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::GetDefaultFonts font family(%s)\n", mFontDefaults->mFontDescription.family.c_str() );
490 fontRun.characterRun.characterIndex = 0;
491 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
492 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
493 fontRun.isDefault = true;
495 fonts.PushBack( fontRun );
499 float Controller::Impl::GetDefaultFontLineHeight()
501 FontId defaultFontId = 0u;
502 if( NULL == mFontDefaults )
504 TextAbstraction::FontDescription fontDescription;
505 defaultFontId = mFontClient.GetFontId( fontDescription );
509 defaultFontId = mFontDefaults->GetFontId( mFontClient );
512 Text::FontMetrics fontMetrics;
513 mMetrics->GetFontMetrics( defaultFontId, fontMetrics );
515 return( fontMetrics.ascender - fontMetrics.descender );
518 void Controller::Impl::OnCursorKeyEvent( const Event& event )
520 if( NULL == mEventData )
522 // Nothing to do if there is no text input.
526 int keyCode = event.p1.mInt;
528 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
530 if( mEventData->mPrimaryCursorPosition > 0u )
532 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
535 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
537 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
539 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
542 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
546 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
551 mEventData->mUpdateCursorPosition = true;
552 mEventData->mScrollAfterUpdatePosition = true;
555 void Controller::Impl::OnTapEvent( const Event& event )
557 if( NULL != mEventData )
559 const unsigned int tapCount = event.p1.mUint;
563 if( IsShowingRealText() )
565 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
566 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
568 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
571 // When the cursor position is changing, delay cursor blinking
572 mEventData->mDecorator->DelayCursorBlink();
576 mEventData->mPrimaryCursorPosition = 0u;
579 mEventData->mUpdateCursorPosition = true;
580 mEventData->mScrollAfterUpdatePosition = true;
582 // Notify the cursor position to the imf manager.
583 if( mEventData->mImfManager )
585 mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition );
586 mEventData->mImfManager.NotifyCursorPosition();
592 void Controller::Impl::OnPanEvent( const Event& event )
594 if( NULL == mEventData )
596 // Nothing to do if there is no text input.
600 int state = event.p1.mInt;
602 if( Gesture::Started == state ||
603 Gesture::Continuing == state )
605 const Vector2& actualSize = mVisualModel->GetActualSize();
606 const Vector2 currentScroll = mEventData->mScrollPosition;
608 if( mEventData->mHorizontalScrollingEnabled )
610 const float displacementX = event.p2.mFloat;
611 mEventData->mScrollPosition.x += displacementX;
613 ClampHorizontalScroll( actualSize );
616 if( mEventData->mVerticalScrollingEnabled )
618 const float displacementY = event.p3.mFloat;
619 mEventData->mScrollPosition.y += displacementY;
621 ClampVerticalScroll( actualSize );
624 if( mEventData->mDecorator )
626 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
631 void Controller::Impl::OnLongPressEvent( const Event& event )
633 DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
635 if ( EventData::EDITING == mEventData->mState )
637 ChangeState ( EventData::EDITING_WITH_POPUP );
638 mEventData->mDecoratorUpdated = true;
642 void Controller::Impl::OnHandleEvent( const Event& event )
644 if( NULL == mEventData )
646 // Nothing to do if there is no text input.
650 const unsigned int state = event.p1.mUint;
651 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
653 if( HANDLE_PRESSED == state )
655 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
656 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
657 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
659 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
661 if( Event::GRAB_HANDLE_EVENT == event.type )
663 ChangeState ( EventData::GRAB_HANDLE_PANNING );
665 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
667 mEventData->mPrimaryCursorPosition = handleNewPosition;
668 mEventData->mUpdateCursorPosition = true;
671 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
673 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
675 if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
676 ( handleNewPosition != mEventData->mRightSelectionPosition ) )
678 mEventData->mLeftSelectionPosition = handleNewPosition;
680 mEventData->mUpdateLeftSelectionPosition = true;
683 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
685 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
687 if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
688 ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
690 mEventData->mRightSelectionPosition = handleNewPosition;
692 mEventData->mUpdateRightSelectionPosition = true;
695 } // end ( HANDLE_PRESSED == state )
696 else if( ( HANDLE_RELEASED == state ) ||
697 handleStopScrolling )
699 CharacterIndex handlePosition = 0u;
700 if( handleStopScrolling )
702 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
703 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
704 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
706 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
709 if( Event::GRAB_HANDLE_EVENT == event.type )
711 mEventData->mUpdateCursorPosition = true;
713 if ( !IsClipboardEmpty() )
715 ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
718 if( handleStopScrolling )
720 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
721 mEventData->mPrimaryCursorPosition = handlePosition;
724 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
726 ChangeState( EventData::SELECTING );
728 if( handleStopScrolling )
730 mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition );
731 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
733 if( mEventData->mUpdateLeftSelectionPosition )
735 mEventData->mLeftSelectionPosition = handlePosition;
739 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
741 ChangeState( EventData::SELECTING );
743 if( handleStopScrolling )
745 mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition );
746 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
747 if( mEventData->mUpdateRightSelectionPosition )
749 mEventData->mRightSelectionPosition = handlePosition;
754 mEventData->mDecoratorUpdated = true;
755 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
756 else if( HANDLE_SCROLLING == state )
758 const float xSpeed = event.p2.mFloat;
759 const Vector2& actualSize = mVisualModel->GetActualSize();
760 const Vector2 currentScrollPosition = mEventData->mScrollPosition;
762 mEventData->mScrollPosition.x += xSpeed;
764 ClampHorizontalScroll( actualSize );
766 bool endOfScroll = false;
767 if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) )
769 // Notify the decorator there is no more text to scroll.
770 // The decorator won't send more scroll events.
771 mEventData->mDecorator->NotifyEndOfScroll();
772 // Still need to set the position of the handle.
776 // Set the position of the handle.
777 const bool scrollRightDirection = xSpeed > 0.f;
778 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
779 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
781 if( Event::GRAB_HANDLE_EVENT == event.type )
783 ChangeState( EventData::GRAB_HANDLE_PANNING );
785 Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
787 // Position the grag handle close to either the left or right edge.
788 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
790 // Get the new handle position.
791 // The grab handle's position is in decorator coords. Need to transforms to text coords.
792 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
793 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
795 mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition;
796 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition;
797 mEventData->mPrimaryCursorPosition = handlePosition;
799 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
801 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
802 // Think if something can be done to save power.
804 ChangeState( EventData::SELECTION_HANDLE_PANNING );
806 Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
808 // Position the selection handle close to either the left or right edge.
809 position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width;
811 // Get the new handle position.
812 // The selection handle's position is in decorator coords. Need to transforms to text coords.
813 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
814 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
816 if( leftSelectionHandleEvent )
818 const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
819 mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles;
820 if( differentHandles )
822 mEventData->mLeftSelectionPosition = handlePosition;
827 const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
828 mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles;
829 if( differentHandles )
831 mEventData->mRightSelectionPosition = handlePosition;
835 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
837 RepositionSelectionHandles();
839 mEventData->mScrollAfterUpdatePosition = true;
842 mEventData->mDecoratorUpdated = true;
843 } // end ( HANDLE_SCROLLING == state )
846 void Controller::Impl::OnSelectEvent( const Event& event )
848 if( NULL == mEventData )
850 // Nothing to do if there is no text.
854 if( mEventData->mSelectionEnabled )
856 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
857 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
858 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
860 // Calculates the logical position from the x,y coords.
861 RepositionSelectionHandles( xPosition,
864 mEventData->mUpdateLeftSelectionPosition = true;
865 mEventData->mUpdateRightSelectionPosition = true;
867 mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
871 void Controller::Impl::OnSelectAllEvent()
873 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
875 if( NULL == mEventData )
877 // Nothing to do if there is no text.
881 if( mEventData->mSelectionEnabled )
883 mEventData->mLeftSelectionPosition = 0u;
884 mEventData->mRightSelectionPosition = mLogicalModel->mText.Count();
886 mEventData->mScrollAfterUpdatePosition = true;
887 mEventData->mUpdateLeftSelectionPosition = true;
888 mEventData->mUpdateRightSelectionPosition = true;
892 void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetrieval )
894 if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition )
896 // Nothing to select if handles are in the same place.
897 selectedText.clear();
901 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
903 //Get start and end position of selection
904 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
905 const Length lengthOfSelectedText = ( handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition ) - startOfSelectedText;
907 // Validate the start and end selection points
908 if( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() )
910 //Get text as a UTF8 string
911 Vector<Character>& utf32Characters = mLogicalModel->mText;
913 Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText );
915 if( deleteAfterRetrieval ) // Only delete text if copied successfully
917 // Delete text between handles
918 Vector<Character>& currentText = mLogicalModel->mText;
920 Vector<Character>::Iterator first = currentText.Begin() + startOfSelectedText;
921 Vector<Character>::Iterator last = first + lengthOfSelectedText;
922 currentText.Erase( first, last );
924 // Scroll after delete.
925 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
926 mEventData->mScrollAfterDelete = true;
928 // Udpade the cursor position and the decorator.
929 // Scroll after the position is updated if is not scrolling after delete.
930 mEventData->mUpdateCursorPosition = true;
931 mEventData->mScrollAfterUpdatePosition = !mEventData->mScrollAfterDelete;
932 mEventData->mDecoratorUpdated = true;
936 void Controller::Impl::ShowClipboard()
940 mClipboard.ShowClipboard();
944 void Controller::Impl::HideClipboard()
948 mClipboard.HideClipboard();
952 bool Controller::Impl::CopyStringToClipboard( std::string& source )
954 //Send string to clipboard
955 return ( mClipboard && mClipboard.SetItem( source ) );
958 void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending )
960 std::string selectedText;
961 RetrieveSelection( selectedText, deleteAfterSending );
962 CopyStringToClipboard( selectedText );
963 ChangeState( EventData::EDITING );
966 void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
970 retrievedString = mClipboard.GetItem( itemIndex );
974 void Controller::Impl::RepositionSelectionHandles()
976 CharacterIndex selectionStart = mEventData->mLeftSelectionPosition;
977 CharacterIndex selectionEnd = mEventData->mRightSelectionPosition;
979 if( selectionStart == selectionEnd )
981 // Nothing to select if handles are in the same place.
985 mEventData->mDecorator->ClearHighlights();
987 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
988 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
989 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
990 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
991 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
992 const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin();
993 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
995 // TODO: Better algorithm to create the highlight box.
998 // Get the height of the line.
999 const Vector<LineRun>& lines = mVisualModel->mLines;
1000 const LineRun& firstLine = *lines.Begin();
1001 const float height = firstLine.ascender + -firstLine.descender;
1003 const bool isLastCharacter = selectionEnd >= mLogicalModel->mText.Count();
1004 const bool startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
1005 const bool endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
1007 // Swap the indices if the start is greater than the end.
1008 const bool indicesSwapped = selectionStart > selectionEnd;
1010 // Tell the decorator to flip the selection handles if needed.
1011 mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
1013 if( indicesSwapped )
1015 std::swap( selectionStart, selectionEnd );
1018 // Get the indices to the first and last selected glyphs.
1019 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
1020 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
1021 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
1022 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
1024 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1025 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
1026 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) );
1028 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
1029 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
1030 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) );
1032 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1034 // Traverse the glyphs.
1035 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
1037 const GlyphInfo& glyph = *( glyphsBuffer + index );
1038 const Vector2& position = *( positionsBuffer + index );
1040 if( splitStartGlyph )
1042 // If the first glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box.
1044 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
1045 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
1046 // Get the direction of the character.
1047 CharacterDirection isCurrentRightToLeft = false;
1048 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1050 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
1053 // The end point could be in the middle of the ligature.
1054 // Calculate the number of characters selected.
1055 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
1057 const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
1059 mEventData->mDecorator->AddHighlight( xPosition,
1061 xPosition + static_cast<float>( numberOfCharacters ) * glyphAdvance,
1062 offset.y + height );
1064 splitStartGlyph = false;
1068 if( splitEndGlyph && ( index == glyphEnd ) )
1070 // Equally, if the last glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box.
1072 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
1073 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
1074 // Get the direction of the character.
1075 CharacterDirection isCurrentRightToLeft = false;
1076 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1078 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
1081 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
1083 const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
1084 mEventData->mDecorator->AddHighlight( xPosition,
1086 xPosition + static_cast<float>( interGlyphIndex ) * glyphAdvance,
1087 offset.y + height );
1089 splitEndGlyph = false;
1093 const float xPosition = position.x - glyph.xBearing + offset.x;
1094 mEventData->mDecorator->AddHighlight( xPosition,
1096 xPosition + glyph.advance,
1097 offset.y + height );
1100 CursorInfo primaryCursorInfo;
1101 GetCursorPosition( mEventData->mLeftSelectionPosition,
1102 primaryCursorInfo );
1104 CursorInfo secondaryCursorInfo;
1105 GetCursorPosition( mEventData->mRightSelectionPosition,
1106 secondaryCursorInfo );
1108 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
1109 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
1111 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
1113 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
1115 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
1116 mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1118 // Set the flag to update the decorator.
1119 mEventData->mDecoratorUpdated = true;
1122 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
1124 if( NULL == mEventData )
1126 // Nothing to do if there is no text input.
1130 if( IsShowingPlaceholderText() )
1132 // Nothing to do if there is the place-holder text.
1136 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1137 const Length numberOfLines = mVisualModel->mLines.Count();
1138 if( 0 == numberOfGlyphs ||
1139 0 == numberOfLines )
1141 // Nothing to do if there is no text.
1145 // Find which word was selected
1146 CharacterIndex selectionStart( 0 );
1147 CharacterIndex selectionEnd( 0 );
1148 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
1149 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
1151 if( selectionStart == selectionEnd )
1153 ChangeState( EventData::EDITING );
1154 // Nothing to select. i.e. a white space, out of bounds
1158 mEventData->mLeftSelectionPosition = selectionStart;
1159 mEventData->mRightSelectionPosition = selectionEnd;
1162 void Controller::Impl::SetPopupButtons()
1165 * Sets the Popup buttons to be shown depending on State.
1167 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1169 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1172 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1174 if( EventData::SELECTING == mEventData->mState )
1176 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
1178 if ( !IsClipboardEmpty() )
1180 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1181 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1184 if ( !mEventData->mAllTextSelected )
1186 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
1189 else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
1191 if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1193 buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
1196 if ( !IsClipboardEmpty() )
1198 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1199 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1202 else if ( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1204 if ( !IsClipboardEmpty() )
1206 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
1207 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
1211 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
1214 void Controller::Impl::ChangeState( EventData::State newState )
1216 if( NULL == mEventData )
1218 // Nothing to do if there is no text input.
1222 DALI_LOG_INFO( gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState );
1224 if( mEventData->mState != newState )
1226 mEventData->mState = newState;
1228 if( EventData::INACTIVE == mEventData->mState )
1230 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1231 mEventData->mDecorator->StopCursorBlink();
1232 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1233 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1234 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1235 mEventData->mDecorator->SetPopupActive( false );
1236 mEventData->mDecoratorUpdated = true;
1239 else if ( EventData::INTERRUPTED == mEventData->mState)
1241 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1242 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1243 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1244 mEventData->mDecorator->SetPopupActive( false );
1245 mEventData->mDecoratorUpdated = true;
1248 else if ( EventData::SELECTING == mEventData->mState )
1250 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1251 mEventData->mDecorator->StopCursorBlink();
1252 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1253 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1254 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1255 if( mEventData->mGrabHandlePopupEnabled )
1258 mEventData->mDecorator->SetPopupActive( true );
1260 mEventData->mDecoratorUpdated = true;
1262 else if( EventData::EDITING == mEventData->mState )
1264 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1265 if( mEventData->mCursorBlinkEnabled )
1267 mEventData->mDecorator->StartCursorBlink();
1269 // Grab handle is not shown until a tap is received whilst EDITING
1270 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1271 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1272 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1273 if( mEventData->mGrabHandlePopupEnabled )
1275 mEventData->mDecorator->SetPopupActive( false );
1277 mEventData->mDecoratorUpdated = true;
1280 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
1282 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState );
1284 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1285 if( mEventData->mCursorBlinkEnabled )
1287 mEventData->mDecorator->StartCursorBlink();
1289 if( mEventData->mSelectionEnabled )
1291 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1292 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1296 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1298 if( mEventData->mGrabHandlePopupEnabled )
1301 mEventData->mDecorator->SetPopupActive( true );
1304 mEventData->mDecoratorUpdated = true;
1306 else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
1308 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState );
1310 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1311 if( mEventData->mCursorBlinkEnabled )
1313 mEventData->mDecorator->StartCursorBlink();
1315 // Grab handle is not shown until a tap is received whilst EDITING
1316 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1317 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1318 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1319 if( mEventData->mGrabHandlePopupEnabled )
1321 mEventData->mDecorator->SetPopupActive( false );
1323 mEventData->mDecoratorUpdated = true;
1326 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
1328 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1329 mEventData->mDecorator->StopCursorBlink();
1330 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
1331 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
1332 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
1333 if( mEventData->mGrabHandlePopupEnabled )
1335 mEventData->mDecorator->SetPopupActive( false );
1337 mEventData->mDecoratorUpdated = true;
1339 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
1341 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState );
1343 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1344 if( mEventData->mCursorBlinkEnabled )
1346 mEventData->mDecorator->StartCursorBlink();
1348 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1349 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1350 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1351 if( mEventData->mGrabHandlePopupEnabled )
1353 mEventData->mDecorator->SetPopupActive( false );
1355 mEventData->mDecoratorUpdated = true;
1357 else if ( EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState )
1359 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState );
1361 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1362 if( mEventData->mCursorBlinkEnabled )
1364 mEventData->mDecorator->StartCursorBlink();
1367 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
1368 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
1369 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
1371 if( mEventData->mGrabHandlePopupEnabled )
1374 mEventData->mDecorator->SetPopupActive( true );
1377 mEventData->mDecoratorUpdated = true;
1382 LineIndex Controller::Impl::GetClosestLine( float y ) const
1384 float totalHeight = 0.f;
1385 LineIndex lineIndex = 0u;
1387 const Vector<LineRun>& lines = mVisualModel->mLines;
1388 for( LineIndex endLine = lines.Count();
1389 lineIndex < endLine;
1392 const LineRun& lineRun = lines[lineIndex];
1393 totalHeight += lineRun.ascender + -lineRun.descender;
1394 if( y < totalHeight )
1400 if( lineIndex == 0 )
1408 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1410 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1411 DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
1413 if ( mLogicalModel->mText.Count() == 0 )
1415 return; // if model empty
1418 if( hitCharacter >= mLogicalModel->mText.Count() )
1420 // Closest hit character is the last character.
1421 if ( hitCharacter == mLogicalModel->mText.Count() )
1423 hitCharacter--; //Hit character index set to last character in logical model
1427 // hitCharacter is out of bounds
1432 startIndex = hitCharacter;
1433 endIndex = hitCharacter;
1434 bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] );
1436 // Find the start and end of the text
1437 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1439 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) )
1444 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1445 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1447 if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) )
1454 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1457 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY );
1459 if( NULL == mEventData )
1461 // Nothing to do if there is no text input.
1465 CharacterIndex logicalIndex = 0u;
1467 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1468 const Length numberOfLines = mVisualModel->mLines.Count();
1469 if( 0 == numberOfGlyphs ||
1470 0 == numberOfLines )
1472 return logicalIndex;
1475 // Find which line is closest
1476 const LineIndex lineIndex = GetClosestLine( visualY );
1477 const LineRun& line = mVisualModel->mLines[lineIndex];
1479 // Get the positions of the glyphs.
1480 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1481 const Vector2* const positionsBuffer = positions.Begin();
1483 // Get the visual to logical conversion tables.
1484 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1485 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1487 // Get the character to glyph conversion table.
1488 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1490 // Get the glyphs per character table.
1491 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1493 // If the vector is void, there is no right to left characters.
1494 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1496 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1497 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1498 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1500 // Whether there is a hit on a glyph.
1501 bool matched = false;
1503 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1504 CharacterIndex visualIndex = startCharacter;
1505 Length numberOfCharacters = 0u;
1506 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1508 // The character in logical order.
1509 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1511 // Get the script of the character.
1512 const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex );
1514 // The number of glyphs for that character
1515 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1516 ++numberOfCharacters;
1519 if( 0u != numberOfGlyphs )
1521 // Get the first character/glyph of the group of glyphs.
1522 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
1523 const CharacterIndex firstLogicalCharacterIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + firstVisualCharacterIndex ) : firstVisualCharacterIndex;
1524 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
1526 // Get the metrics for the group of glyphs.
1527 GlyphMetrics glyphMetrics;
1528 GetGlyphsMetrics( firstLogicalGlyphIndex,
1534 // Get the position of the first glyph.
1535 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
1537 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
1538 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
1539 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
1540 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
1542 GlyphIndex index = 0u;
1543 for( ; !matched && ( index < numberOfBlocks ); ++index )
1545 // Find the mid-point of the area containing the glyph
1546 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
1548 if( visualX < glyphCenter )
1557 visualIndex = firstVisualCharacterIndex + index;
1561 numberOfCharacters = 0u;
1567 // Return the logical position of the cursor in characters.
1571 visualIndex = endCharacter;
1574 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1575 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1577 DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
1579 return logicalIndex;
1582 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1583 CursorInfo& cursorInfo )
1585 // TODO: Check for multiline with \n, etc...
1587 const Length numberOfCharacters = mLogicalModel->mText.Count();
1588 if( !IsShowingRealText() )
1590 // Do not want to use the place-holder text to set the cursor position.
1592 // Use the line's height of the font's family set to set the cursor's size.
1593 // If there is no font's family set, use the default font.
1594 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1596 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1597 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1599 switch( mLayoutEngine.GetHorizontalAlignment() )
1601 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1603 cursorInfo.primaryPosition.x = 0.f;
1606 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1608 cursorInfo.primaryPosition.x = floorf( 0.5f * mVisualModel->mControlSize.width );
1611 case LayoutEngine::HORIZONTAL_ALIGN_END:
1613 cursorInfo.primaryPosition.x = mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1618 switch( mLayoutEngine.GetVerticalAlignment() )
1620 case LayoutEngine::VERTICAL_ALIGN_TOP:
1622 cursorInfo.primaryPosition.y = 0.f;
1625 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1627 cursorInfo.primaryPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - cursorInfo.lineHeight ) );
1630 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1632 cursorInfo.primaryPosition.y = mVisualModel->mControlSize.height - cursorInfo.lineHeight;
1637 // Nothing else to do.
1641 // Check if the logical position is the first or the last one of the text.
1642 const bool isFirstPosition = 0u == logical;
1643 const bool isLastPosition = numberOfCharacters == logical;
1645 // 'logical' is the logical 'cursor' index.
1646 // Get the next and current logical 'character' index.
1647 const CharacterIndex nextCharacterIndex = logical;
1648 const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
1650 // Get the direction of the character and the next one.
1651 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1653 CharacterDirection isCurrentRightToLeft = false;
1654 CharacterDirection isNextRightToLeft = false;
1655 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1657 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
1658 isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
1661 // Get the line where the character is laid-out.
1662 const LineRun* const modelLines = mVisualModel->mLines.Begin();
1664 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex );
1665 const LineRun& line = *( modelLines + lineIndex );
1667 // Get the paragraph's direction.
1668 const CharacterDirection isRightToLeftParagraph = line.direction;
1670 // Check whether there is an alternative position:
1672 cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
1673 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1675 // Set the line height.
1676 cursorInfo.lineHeight = line.ascender + -line.descender;
1678 // Calculate the primary cursor.
1680 CharacterIndex index = characterIndex;
1681 if( cursorInfo.isSecondaryCursor )
1683 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
1685 if( isLastPosition )
1687 // The position of the cursor after the last character needs special
1688 // care depending on its direction and the direction of the paragraph.
1690 // Need to find the first character after the last character with the paragraph's direction.
1691 // i.e l0 l1 l2 r0 r1 should find r0.
1693 // TODO: check for more than one line!
1694 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1695 index = mLogicalModel->GetLogicalCharacterIndex( index );
1699 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
1703 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1704 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1705 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1706 const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin();
1707 const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin();
1709 // Convert the cursor position into the glyph position.
1710 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
1711 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1712 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
1714 // Get the metrics for the group of glyphs.
1715 GlyphMetrics glyphMetrics;
1716 GetGlyphsMetrics( primaryGlyphIndex,
1717 primaryNumberOfGlyphs,
1722 // Whether to add the glyph's advance to the cursor position.
1723 // i.e if the paragraph is left to right and the logical cursor is zero, the position is the position of the first glyph and the advance is not added,
1724 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
1725 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
1746 // Where F -> isFirstPosition
1747 // L -> isLastPosition
1748 // C -> isCurrentRightToLeft
1749 // P -> isRightToLeftParagraph
1750 // A -> Whether to add the glyph's advance.
1752 const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
1753 ( isFirstPosition && isRightToLeftParagraph ) ||
1754 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
1756 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
1758 if( !isLastPosition &&
1759 ( primaryNumberOfCharacters > 1u ) )
1761 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
1763 bool isCurrentRightToLeft = false;
1764 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1766 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
1769 Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
1770 if( isCurrentRightToLeft )
1772 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
1775 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
1778 // Get the glyph position and x bearing.
1779 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
1781 // Set the primary cursor's height.
1782 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
1784 // Set the primary cursor's position.
1785 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
1786 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1788 // Calculate the secondary cursor.
1790 if( cursorInfo.isSecondaryCursor )
1792 // Set the secondary cursor's height.
1793 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1795 CharacterIndex index = characterIndex;
1796 if( !isLastPosition )
1798 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
1801 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
1802 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
1804 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
1806 GetGlyphsMetrics( secondaryGlyphIndex,
1807 secondaryNumberOfGlyphs,
1812 // Set the secondary cursor's position.
1813 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
1814 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1818 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1820 if( NULL == mEventData )
1822 // Nothing to do if there is no text input.
1826 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1828 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1829 const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1831 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1832 Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1834 if( numberOfCharacters > 1u )
1836 const Script script = mLogicalModel->GetScript( index );
1837 if( HasLigatureMustBreak( script ) )
1839 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ﻻ, ...
1840 numberOfCharacters = 1u;
1845 while( 0u == numberOfCharacters )
1848 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1852 if( index < mEventData->mPrimaryCursorPosition )
1854 cursorIndex -= numberOfCharacters;
1858 cursorIndex += numberOfCharacters;
1864 void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo )
1866 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1867 if( NULL == mEventData )
1869 // Nothing to do if there is no text input.
1870 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1874 const Vector2 offset = mEventData->mScrollPosition + ( IsShowingRealText() ? mAlignmentOffset : Vector2::ZERO );
1875 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1877 // Sets the cursor position.
1878 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1881 cursorInfo.primaryCursorHeight,
1882 cursorInfo.lineHeight );
1883 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1885 // Sets the grab handle position.
1886 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1889 cursorInfo.lineHeight );
1891 if( cursorInfo.isSecondaryCursor )
1893 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1894 cursorInfo.secondaryPosition.x + offset.x,
1895 cursorInfo.secondaryPosition.y + offset.y,
1896 cursorInfo.secondaryCursorHeight,
1897 cursorInfo.lineHeight );
1898 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1901 // Set which cursors are active according the state.
1902 if( EventData::IsEditingState( mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) )
1904 if( cursorInfo.isSecondaryCursor )
1906 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1910 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1915 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
1918 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1921 void Controller::Impl::UpdateSelectionHandle( HandleType handleType,
1922 const CursorInfo& cursorInfo )
1924 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1925 ( RIGHT_SELECTION_HANDLE != handleType ) )
1930 const Vector2 cursorPosition = cursorInfo.primaryPosition + mEventData->mScrollPosition + mAlignmentOffset;
1932 // Sets the handle's position.
1933 mEventData->mDecorator->SetPosition( handleType,
1936 cursorInfo.lineHeight );
1938 // If selection handle at start of the text and other at end of the text then all text is selected.
1939 const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1940 const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition );
1941 mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() );
1944 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1946 // Clamp between -space & 0 (and the text alignment).
1948 if( actualSize.width > mVisualModel->mControlSize.width )
1950 const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x;
1951 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1952 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1954 mEventData->mDecoratorUpdated = true;
1958 mEventData->mScrollPosition.x = 0.f;
1962 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1964 // Clamp between -space & 0 (and the text alignment).
1965 if( actualSize.height > mVisualModel->mControlSize.height )
1967 const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y;
1968 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1969 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1971 mEventData->mDecoratorUpdated = true;
1975 mEventData->mScrollPosition.y = 0.f;
1979 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1981 // position is in actor's coords.
1982 const float positionEnd = position.x + ( mEventData->mDecorator ? mEventData->mDecorator->GetCursorWidth() : 0.f );
1984 // Transform the position to decorator coords.
1985 const float offset = mEventData->mScrollPosition.x + mAlignmentOffset.x;
1986 const float decoratorPositionBegin = position.x + offset;
1987 const float decoratorPositionEnd = positionEnd + offset;
1989 if( decoratorPositionBegin < 0.f )
1991 mEventData->mScrollPosition.x = -position.x - mAlignmentOffset.x;
1993 else if( decoratorPositionEnd > mVisualModel->mControlSize.width )
1995 mEventData->mScrollPosition.x = mVisualModel->mControlSize.width - positionEnd - mAlignmentOffset.x;
1999 void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo )
2001 // Get the current cursor position in decorator coords.
2002 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
2004 // Calculate the offset to match the cursor position before the character was deleted.
2005 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
2007 ClampHorizontalScroll( mVisualModel->GetActualSize() );
2010 void Controller::Impl::RequestRelayout()
2012 mControlInterface.RequestTextRelayout();
2017 } // namespace Toolkit