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/layouts/layout-parameters.h>
29 #include <dali-toolkit/internal/text/multi-language-support.h>
30 #include <dali-toolkit/internal/text/script-run.h>
31 #include <dali-toolkit/internal/text/segmentation.h>
32 #include <dali-toolkit/internal/text/shaper.h>
33 #include <dali-toolkit/internal/text/text-io.h>
34 #include <dali-toolkit/internal/text/text-view.h>
39 #if defined(DEBUG_ENABLED)
40 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_CONTROLS");
44 * @brief Some characters can be shaped in more than one glyph.
45 * This struct is used to retrieve metrics from these group of glyphs.
59 float fontHeight; ///< The font's height of that glyphs.
60 float advance; ///< The sum of all the advances of all the glyphs.
61 float ascender; ///< The font's ascender.
62 float xBearing; ///< The x bearing of the first glyph.
65 const std::string EMPTY_STRING("");
79 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
81 * @param[in] glyphIndex The index to the first glyph.
82 * @param[in] numberOfGlyphs The number of glyphs.
83 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
87 void GetGlyphsMetrics( GlyphIndex glyphIndex,
88 Length numberOfGlyphs,
89 GlyphMetrics& glyphMetrics,
90 VisualModelPtr visualModel,
91 TextAbstraction::FontClient& fontClient )
93 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
95 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
97 Text::FontMetrics fontMetrics;
98 fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics );
100 glyphMetrics.fontHeight = fontMetrics.height;
101 glyphMetrics.advance = firstGlyph.advance;
102 glyphMetrics.ascender = fontMetrics.ascender;
103 glyphMetrics.xBearing = firstGlyph.xBearing;
105 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
107 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
109 glyphMetrics.advance += glyphInfo.advance;
113 EventData::EventData( DecoratorPtr decorator )
114 : mDecorator( decorator ),
115 mPlaceholderTextActive(),
116 mPlaceholderTextInactive(),
117 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
121 mPrimaryCursorPosition( 0u ),
122 mLeftSelectionPosition( 0u ),
123 mRightSelectionPosition( 0u ),
124 mPreEditStartPosition( 0u ),
125 mPreEditLength( 0u ),
126 mIsShowingPlaceholderText( false ),
127 mPreEditFlag( false ),
128 mDecoratorUpdated( false ),
129 mCursorBlinkEnabled( true ),
130 mGrabHandleEnabled( true ),
131 mGrabHandlePopupEnabled( true ),
132 mSelectionEnabled( true ),
133 mHorizontalScrollingEnabled( true ),
134 mVerticalScrollingEnabled( false ),
135 mUpdateCursorPosition( false ),
136 mUpdateLeftSelectionPosition( false ),
137 mUpdateRightSelectionPosition( false ),
138 mScrollAfterUpdatePosition( false ),
139 mScrollAfterDelete( false )
142 EventData::~EventData()
145 bool Controller::Impl::ProcessInputEvents()
147 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
148 if( NULL == mEventData )
150 // Nothing to do if there is no text input.
151 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
155 if( mEventData->mDecorator )
157 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
158 iter != mEventData->mEventQueue.end();
163 case Event::CURSOR_KEY_EVENT:
165 OnCursorKeyEvent( *iter );
168 case Event::TAP_EVENT:
173 case Event::PAN_EVENT:
178 case Event::GRAB_HANDLE_EVENT:
179 case Event::LEFT_SELECTION_HANDLE_EVENT:
180 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
182 OnHandleEvent( *iter );
189 // The cursor must also be repositioned after inserts into the model
190 if( mEventData->mUpdateCursorPosition )
192 // Updates the cursor position and scrolls the text to make it visible.
194 UpdateCursorPosition();
196 if( mEventData->mScrollAfterUpdatePosition )
198 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
200 ScrollToMakePositionVisible( primaryCursorPosition );
201 mEventData->mScrollAfterUpdatePosition = false;
204 mEventData->mDecoratorUpdated = true;
205 mEventData->mUpdateCursorPosition = false;
207 else if( mEventData->mScrollAfterDelete )
209 ScrollTextToMatchCursor();
210 mEventData->mDecoratorUpdated = true;
211 mEventData->mScrollAfterDelete = false;
215 bool leftScroll = false;
216 bool rightScroll = false;
218 if( mEventData->mUpdateLeftSelectionPosition )
220 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
222 if( mEventData->mScrollAfterUpdatePosition )
224 const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE );
226 ScrollToMakePositionVisible( leftHandlePosition );
230 mEventData->mDecoratorUpdated = true;
231 mEventData->mUpdateLeftSelectionPosition = false;
234 if( mEventData->mUpdateRightSelectionPosition )
236 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
238 if( mEventData->mScrollAfterUpdatePosition )
240 const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE );
242 ScrollToMakePositionVisible( rightHandlePosition );
246 mEventData->mDecoratorUpdated = true;
247 mEventData->mUpdateRightSelectionPosition = false;
250 if( leftScroll || rightScroll )
252 mEventData->mScrollAfterUpdatePosition = false;
256 mEventData->mEventQueue.clear();
258 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
260 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
261 mEventData->mDecoratorUpdated = false;
263 return decoratorUpdated;
266 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
268 // Calculate the operations to be done.
269 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
271 Vector<Character>& utf32Characters = mLogicalModel->mText;
273 const Length numberOfCharacters = utf32Characters.Count();
275 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
276 if( GET_LINE_BREAKS & operations )
278 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
279 // calculate the bidirectional info for each 'paragraph'.
280 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
281 // is not shaped together).
282 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
284 SetLineBreakInfo( utf32Characters,
288 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
289 if( GET_WORD_BREAKS & operations )
291 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
292 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
294 SetWordBreakInfo( utf32Characters,
298 const bool getScripts = GET_SCRIPTS & operations;
299 const bool validateFonts = VALIDATE_FONTS & operations;
301 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
302 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
304 if( getScripts || validateFonts )
306 // Validates the fonts assigned by the application or assigns default ones.
307 // It makes sure all the characters are going to be rendered by the correct font.
308 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
312 // Retrieves the scripts used in the text.
313 multilanguageSupport.SetScripts( utf32Characters,
320 if( 0u == validFonts.Count() )
322 // Copy the requested font defaults received via the property system.
323 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
324 GetDefaultFonts( validFonts, numberOfCharacters );
327 // Validates the fonts. If there is a character with no assigned font it sets a default one.
328 // After this call, fonts are validated.
329 multilanguageSupport.ValidateFonts( utf32Characters,
335 Vector<Character> mirroredUtf32Characters;
336 bool textMirrored = false;
337 if( BIDI_INFO & operations )
339 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
340 // bidirectional info.
342 Length numberOfParagraphs = 0u;
344 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
345 for( Length index = 0u; index < numberOfCharacters; ++index )
347 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
349 ++numberOfParagraphs;
353 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
354 bidirectionalInfo.Reserve( numberOfParagraphs );
356 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
357 SetBidirectionalInfo( utf32Characters,
362 if( 0u != bidirectionalInfo.Count() )
364 // This paragraph has right to left text. Some characters may need to be mirrored.
365 // TODO: consider if the mirrored string can be stored as well.
367 textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
369 // Only set the character directions if there is right to left characters.
370 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
371 directions.Resize( numberOfCharacters );
373 GetCharactersDirection( bidirectionalInfo,
378 // There is no right to left characters. Clear the directions vector.
379 mLogicalModel->mCharacterDirections.Clear();
384 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
385 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
386 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
387 if( SHAPE_TEXT & operations )
389 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
391 ShapeText( textToShape,
396 glyphsToCharactersMap,
397 charactersPerGlyph );
399 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
400 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
401 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
404 const Length numberOfGlyphs = glyphs.Count();
406 if( GET_GLYPH_METRICS & operations )
408 mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
412 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
417 fontRun.characterRun.characterIndex = 0;
418 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
419 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
420 fontRun.isDefault = true;
422 fonts.PushBack( fontRun );
426 void Controller::Impl::OnCursorKeyEvent( const Event& event )
428 if( NULL == mEventData )
430 // Nothing to do if there is no text input.
434 int keyCode = event.p1.mInt;
436 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
438 if( mEventData->mPrimaryCursorPosition > 0u )
440 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
443 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
445 if( mLogicalModel->GetNumberOfCharacters() > mEventData->mPrimaryCursorPosition )
447 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
450 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
454 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
459 mEventData->mUpdateCursorPosition = true;
460 mEventData->mScrollAfterUpdatePosition = true;
463 void Controller::Impl::OnTapEvent( const Event& event )
465 if( NULL != mEventData )
467 const unsigned int tapCount = event.p1.mUint;
471 if( ! IsShowingPlaceholderText() )
473 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
474 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
476 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
481 mEventData->mPrimaryCursorPosition = 0u;
484 mEventData->mUpdateCursorPosition = true;
485 mEventData->mScrollAfterUpdatePosition = true;
487 else if( mEventData->mSelectionEnabled &&
490 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
491 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
492 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
494 RepositionSelectionHandles( xPosition,
497 mEventData->mScrollAfterUpdatePosition = true;
498 mEventData->mUpdateLeftSelectionPosition = true;
499 mEventData->mUpdateRightSelectionPosition = true;
504 void Controller::Impl::OnPanEvent( const Event& event )
506 if( NULL == mEventData )
508 // Nothing to do if there is no text input.
512 int state = event.p1.mInt;
514 if( Gesture::Started == state ||
515 Gesture::Continuing == state )
517 const Vector2& actualSize = mVisualModel->GetActualSize();
518 const Vector2 currentScroll = mEventData->mScrollPosition;
520 if( mEventData->mHorizontalScrollingEnabled )
522 const float displacementX = event.p2.mFloat;
523 mEventData->mScrollPosition.x += displacementX;
525 ClampHorizontalScroll( actualSize );
528 if( mEventData->mVerticalScrollingEnabled )
530 const float displacementY = event.p3.mFloat;
531 mEventData->mScrollPosition.y += displacementY;
533 ClampVerticalScroll( actualSize );
536 if( mEventData->mDecorator )
538 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
543 void Controller::Impl::OnHandleEvent( const Event& event )
545 if( NULL == mEventData )
547 // Nothing to do if there is no text input.
551 const unsigned int state = event.p1.mUint;
552 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
554 if( HANDLE_PRESSED == state )
556 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
557 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
558 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
560 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
562 if( Event::GRAB_HANDLE_EVENT == event.type )
564 ChangeState ( EventData::GRAB_HANDLE_PANNING );
566 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
568 mEventData->mPrimaryCursorPosition = handleNewPosition;
569 mEventData->mUpdateCursorPosition = true;
572 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
574 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
576 if( handleNewPosition != mEventData->mLeftSelectionPosition )
578 mEventData->mLeftSelectionPosition = handleNewPosition;
580 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
581 mEventData->mRightSelectionPosition );
583 mEventData->mUpdateLeftSelectionPosition = true;
586 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
588 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
590 if( handleNewPosition != mEventData->mRightSelectionPosition )
592 mEventData->mRightSelectionPosition = handleNewPosition;
594 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
595 mEventData->mRightSelectionPosition );
597 mEventData->mUpdateRightSelectionPosition = true;
600 } // end ( HANDLE_PRESSED == state )
601 else if( ( HANDLE_RELEASED == state ) ||
602 handleStopScrolling )
604 CharacterIndex handlePosition = 0u;
605 if( handleStopScrolling )
607 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
608 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
609 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
611 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
614 if( Event::GRAB_HANDLE_EVENT == event.type )
616 mEventData->mUpdateCursorPosition = true;
618 ChangeState( EventData::EDITING_WITH_POPUP );
620 if( handleStopScrolling )
622 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
623 mEventData->mPrimaryCursorPosition = handlePosition;
626 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
628 ChangeState( EventData::SELECTING );
630 if( handleStopScrolling )
632 mEventData->mUpdateLeftSelectionPosition = mEventData->mLeftSelectionPosition != handlePosition;
633 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
634 mEventData->mLeftSelectionPosition = handlePosition;
636 if( mEventData->mUpdateLeftSelectionPosition )
638 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
639 mEventData->mRightSelectionPosition );
643 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
645 ChangeState( EventData::SELECTING );
647 if( handleStopScrolling )
649 mEventData->mUpdateRightSelectionPosition = mEventData->mRightSelectionPosition != handlePosition;
650 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
651 mEventData->mRightSelectionPosition = handlePosition;
653 if( mEventData->mUpdateRightSelectionPosition )
655 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
656 mEventData->mRightSelectionPosition );
661 mEventData->mDecoratorUpdated = true;
662 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
663 else if( HANDLE_SCROLLING == state )
665 const float xSpeed = event.p2.mFloat;
666 const Vector2& actualSize = mVisualModel->GetActualSize();
668 mEventData->mScrollPosition.x += xSpeed;
670 ClampHorizontalScroll( actualSize );
672 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
673 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
675 if( Event::GRAB_HANDLE_EVENT == event.type )
677 ChangeState( EventData::GRAB_HANDLE_PANNING );
679 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
681 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
682 // Think if something can be done to save power.
684 ChangeState( EventData::SELECTION_HANDLE_PANNING );
686 const Vector2& position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
688 // Get the new handle position.
689 // The selection handle's position is in decorator coords. Need to transforms to text coords.
690 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
691 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
693 if( leftSelectionHandleEvent )
695 mEventData->mUpdateLeftSelectionPosition = handlePosition != mEventData->mLeftSelectionPosition;
696 mEventData->mLeftSelectionPosition = handlePosition;
700 mEventData->mUpdateRightSelectionPosition = handlePosition != mEventData->mRightSelectionPosition;
701 mEventData->mRightSelectionPosition = handlePosition;
704 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
706 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
707 mEventData->mRightSelectionPosition );
710 mEventData->mDecoratorUpdated = true;
711 } // end ( HANDLE_SCROLLING == state )
714 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
716 mEventData->mDecorator->ClearHighlights();
718 mEventData->mLeftSelectionPosition = selectionStart;
719 mEventData->mRightSelectionPosition = selectionEnd;
721 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
722 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
723 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
724 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
726 // TODO: Better algorithm to create the highlight box.
729 const Vector<LineRun>& lines = mVisualModel->mLines;
730 const float height = lines[0].ascender + -lines[0].descender;
732 const bool indicesSwapped = selectionStart > selectionEnd;
735 std::swap( selectionStart, selectionEnd );
738 GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
739 GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) ) - 1u;
741 mEventData->mDecorator->SwapSelectionHandlesEnabled( indicesSwapped );
743 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
745 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
747 const GlyphInfo& glyph = *( glyphsBuffer + index );
748 const Vector2& position = *( positionsBuffer + index );
750 const float xPosition = position.x - glyph.xBearing + offset.x;
751 mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
754 CursorInfo primaryCursorInfo;
755 GetCursorPosition( mEventData->mLeftSelectionPosition,
758 CursorInfo secondaryCursorInfo;
759 GetCursorPosition( mEventData->mRightSelectionPosition,
760 secondaryCursorInfo );
762 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
763 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
765 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
767 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
769 // Set the flag to update the decorator.
770 mEventData->mDecoratorUpdated = true;
773 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
775 if( NULL == mEventData )
777 // Nothing to do if there is no text input.
781 if( IsShowingPlaceholderText() )
783 // Nothing to do if there is the place-holder text.
787 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
788 const Length numberOfLines = mVisualModel->mLines.Count();
789 if( 0 == numberOfGlyphs ||
792 // Nothing to do if there is no text.
796 // Find which word was selected
797 CharacterIndex selectionStart( 0 );
798 CharacterIndex selectionEnd( 0 );
799 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
800 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
802 if( selectionStart == selectionEnd )
804 ChangeState( EventData::EDITING );
805 // Nothing to select. i.e. a white space, out of bounds
809 RepositionSelectionHandles( selectionStart, selectionEnd );
812 void Controller::Impl::ChangeState( EventData::State newState )
814 if( NULL == mEventData )
816 // Nothing to do if there is no text input.
820 if( mEventData->mState != newState )
822 mEventData->mState = newState;
824 if( EventData::INACTIVE == mEventData->mState )
826 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
827 mEventData->mDecorator->StopCursorBlink();
828 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
829 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
830 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
831 mEventData->mDecorator->SetPopupActive( false );
832 mEventData->mDecoratorUpdated = true;
834 else if ( EventData::SELECTING == mEventData->mState )
836 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
837 mEventData->mDecorator->StopCursorBlink();
838 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
839 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
840 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
841 if( mEventData->mGrabHandlePopupEnabled )
843 mEventData->mDecorator->SetPopupActive( true );
845 mEventData->mDecoratorUpdated = true;
847 else if( EventData::EDITING == mEventData->mState )
849 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
850 if( mEventData->mCursorBlinkEnabled )
852 mEventData->mDecorator->StartCursorBlink();
854 // Grab handle is not shown until a tap is received whilst EDITING
855 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
856 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
857 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
858 if( mEventData->mGrabHandlePopupEnabled )
860 mEventData->mDecorator->SetPopupActive( false );
862 mEventData->mDecoratorUpdated = true;
864 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
866 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
867 if( mEventData->mCursorBlinkEnabled )
869 mEventData->mDecorator->StartCursorBlink();
871 if( mEventData->mSelectionEnabled )
873 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
874 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
878 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
880 if( mEventData->mGrabHandlePopupEnabled )
882 mEventData->mDecorator->SetPopupActive( true );
884 mEventData->mDecoratorUpdated = true;
886 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
888 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
889 mEventData->mDecorator->StopCursorBlink();
890 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
891 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
892 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
893 if( mEventData->mGrabHandlePopupEnabled )
895 mEventData->mDecorator->SetPopupActive( false );
897 mEventData->mDecoratorUpdated = true;
899 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
901 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
902 if( mEventData->mCursorBlinkEnabled )
904 mEventData->mDecorator->StartCursorBlink();
906 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
907 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
908 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
909 if( mEventData->mGrabHandlePopupEnabled )
911 mEventData->mDecorator->SetPopupActive( false );
913 mEventData->mDecoratorUpdated = true;
918 LineIndex Controller::Impl::GetClosestLine( float y ) const
920 float totalHeight = 0.f;
921 LineIndex lineIndex = 0u;
923 const Vector<LineRun>& lines = mVisualModel->mLines;
924 for( LineIndex endLine = lines.Count();
928 const LineRun& lineRun = lines[lineIndex];
929 totalHeight += lineRun.ascender + -lineRun.descender;
930 if( y < totalHeight )
944 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
946 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
947 if( hitCharacter >= mLogicalModel->mText.Count() )
949 // Selection out of bounds.
953 startIndex = hitCharacter;
954 endIndex = hitCharacter;
956 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
958 // Find the start and end of the text
959 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
961 Character charCode = mLogicalModel->mText[ startIndex-1 ];
962 if( TextAbstraction::IsWhiteSpace( charCode ) )
967 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
968 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
970 Character charCode = mLogicalModel->mText[ endIndex ];
971 if( TextAbstraction::IsWhiteSpace( charCode ) )
979 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
982 if( NULL == mEventData )
984 // Nothing to do if there is no text input.
988 CharacterIndex logicalIndex = 0u;
990 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
991 const Length numberOfLines = mVisualModel->mLines.Count();
992 if( 0 == numberOfGlyphs ||
998 // Find which line is closest
999 const LineIndex lineIndex = GetClosestLine( visualY );
1000 const LineRun& line = mVisualModel->mLines[lineIndex];
1002 // Get the positions of the glyphs.
1003 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1004 const Vector2* const positionsBuffer = positions.Begin();
1006 // Get the visual to logical conversion tables.
1007 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1008 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1010 // Get the character to glyph conversion table.
1011 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1013 // Get the glyphs per character table.
1014 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1016 // If the vector is void, there is no right to left characters.
1017 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1019 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1020 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1021 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1023 // Whether there is a hit on a glyph.
1024 bool matched = false;
1026 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1027 CharacterIndex visualIndex = startCharacter;
1028 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1030 // The character in logical order.
1031 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1033 // The first glyph for that character in logical order.
1034 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1036 // The number of glyphs for that character
1037 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1039 // Get the metrics for the group of glyphs.
1040 GlyphMetrics glyphMetrics;
1041 GetGlyphsMetrics( glyphLogicalOrderIndex,
1047 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1049 // Find the mid-point of the area containing the glyph
1050 const float glyphCenter = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
1052 if( visualX < glyphCenter )
1059 // Return the logical position of the cursor in characters.
1063 visualIndex = endCharacter;
1066 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1067 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1068 return logicalIndex;
1071 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1072 CursorInfo& cursorInfo )
1074 // TODO: Check for multiline with \n, etc...
1076 // Check if the logical position is the first or the last one of the text.
1077 const bool isFirstPosition = 0u == logical;
1078 const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
1080 if( isFirstPosition && isLastPosition )
1082 // There is zero characters. Get the default font.
1084 FontId defaultFontId = 0u;
1085 if( NULL == mFontDefaults )
1087 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1092 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1095 Text::FontMetrics fontMetrics;
1096 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1098 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
1099 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1101 cursorInfo.primaryPosition.x = 0.f;
1102 cursorInfo.primaryPosition.y = 0.f;
1104 // Nothing else to do.
1108 // Get the previous logical index.
1109 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
1111 // Decrease the logical index if it's the last one.
1112 if( isLastPosition )
1117 // Get the direction of the character and the previous one.
1118 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1120 CharacterDirection isCurrentRightToLeft = false;
1121 CharacterDirection isPreviousRightToLeft = false;
1122 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1124 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
1125 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
1128 // Get the line where the character is laid-out.
1129 const LineRun* modelLines = mVisualModel->mLines.Begin();
1131 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
1132 const LineRun& line = *( modelLines + lineIndex );
1134 // Get the paragraph's direction.
1135 const CharacterDirection isRightToLeftParagraph = line.direction;
1137 // Check whether there is an alternative position:
1139 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
1140 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1142 // Set the line height.
1143 cursorInfo.lineHeight = line.ascender + -line.descender;
1145 // Convert the cursor position into the glyph position.
1146 CharacterIndex characterIndex = logical;
1147 if( cursorInfo.isSecondaryCursor &&
1148 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
1150 characterIndex = previousLogical;
1153 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1154 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1155 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
1157 // Get the metrics for the group of glyphs.
1158 GlyphMetrics glyphMetrics;
1159 GetGlyphsMetrics( currentGlyphIndex,
1165 float interGlyphAdvance = 0.f;
1166 if( !isLastPosition &&
1167 ( numberOfCharacters > 1u ) )
1169 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
1170 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
1173 // Get the glyph position and x bearing.
1174 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
1176 // Set the cursor's height.
1177 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
1179 // Set the position.
1180 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
1181 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1183 if( isLastPosition )
1185 // The position of the cursor after the last character needs special
1186 // care depending on its direction and the direction of the paragraph.
1188 if( cursorInfo.isSecondaryCursor )
1190 // Need to find the first character after the last character with the paragraph's direction.
1191 // i.e l0 l1 l2 r0 r1 should find r0.
1193 // TODO: check for more than one line!
1194 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1195 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
1197 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1198 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1200 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
1202 // Get the metrics for the group of glyphs.
1203 GlyphMetrics glyphMetrics;
1204 GetGlyphsMetrics( glyphIndex,
1210 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1212 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1216 if( !isCurrentRightToLeft )
1218 cursorInfo.primaryPosition.x += glyphMetrics.advance;
1222 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1227 // Set the alternative cursor position.
1228 if( cursorInfo.isSecondaryCursor )
1230 // Convert the cursor position into the glyph position.
1231 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1232 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1233 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1235 // Get the glyph position.
1236 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1238 // Get the metrics for the group of glyphs.
1239 GlyphMetrics glyphMetrics;
1240 GetGlyphsMetrics( previousGlyphIndex,
1246 // Set the cursor position and height.
1247 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1248 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1250 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1252 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1254 // Update the primary cursor height as well.
1255 cursorInfo.primaryCursorHeight *= 0.5f;
1259 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1261 if( NULL == mEventData )
1263 // Nothing to do if there is no text input.
1267 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1269 const Script script = mLogicalModel->GetScript( index );
1270 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1271 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1273 Length numberOfCharacters = 0u;
1274 if( TextAbstraction::LATIN == script )
1276 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1277 numberOfCharacters = 1u;
1281 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1282 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1284 while( 0u == numberOfCharacters )
1286 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1291 if( index < mEventData->mPrimaryCursorPosition )
1293 cursorIndex -= numberOfCharacters;
1297 cursorIndex += numberOfCharacters;
1303 void Controller::Impl::UpdateCursorPosition()
1305 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1306 if( NULL == mEventData )
1308 // Nothing to do if there is no text input.
1309 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1313 if( IsShowingPlaceholderText() )
1315 // Do not want to use the place-holder text to set the cursor position.
1317 // Use the line's height of the font's family set to set the cursor's size.
1318 // If there is no font's family set, use the default font.
1319 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1321 float lineHeight = 0.f;
1323 FontId defaultFontId = 0u;
1324 if( NULL == mFontDefaults )
1326 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1331 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1334 Text::FontMetrics fontMetrics;
1335 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1337 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1340 Vector2 cursorPosition;
1342 switch( mLayoutEngine.GetHorizontalAlignment() )
1344 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1346 cursorPosition.x = 1.f;
1349 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1351 cursorPosition.x = floor( 0.5f * mControlSize.width );
1354 case LayoutEngine::HORIZONTAL_ALIGN_END:
1356 cursorPosition.x = mControlSize.width;
1361 switch( mLayoutEngine.GetVerticalAlignment() )
1363 case LayoutEngine::VERTICAL_ALIGN_TOP:
1365 cursorPosition.y = 0.f;
1368 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1370 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1373 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1375 cursorPosition.y = mControlSize.height - lineHeight;
1380 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1388 CursorInfo cursorInfo;
1389 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1392 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1393 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1395 // Sets the cursor position.
1396 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1399 cursorInfo.primaryCursorHeight,
1400 cursorInfo.lineHeight );
1401 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1403 // Sets the grab handle position.
1404 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1407 cursorInfo.lineHeight );
1409 if( cursorInfo.isSecondaryCursor )
1411 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1412 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1413 cursorInfo.secondaryPosition.x + offset.x,
1414 cursorInfo.secondaryPosition.y + offset.y,
1415 cursorInfo.secondaryCursorHeight,
1416 cursorInfo.lineHeight );
1417 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1421 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1424 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1427 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1429 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1430 ( RIGHT_SELECTION_HANDLE != handleType ) )
1435 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1436 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1438 CursorInfo cursorInfo;
1439 GetCursorPosition( index,
1442 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1443 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1445 // Sets the grab handle position.
1446 mEventData->mDecorator->SetPosition( handleType,
1449 cursorInfo.lineHeight );
1452 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1454 // Clamp between -space & 0 (and the text alignment).
1455 if( actualSize.width > mControlSize.width )
1457 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1458 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1459 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1461 mEventData->mDecoratorUpdated = true;
1465 mEventData->mScrollPosition.x = 0.f;
1469 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1471 // Clamp between -space & 0 (and the text alignment).
1472 if( actualSize.height > mControlSize.height )
1474 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1475 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1476 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1478 mEventData->mDecoratorUpdated = true;
1482 mEventData->mScrollPosition.y = 0.f;
1486 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1489 bool updateDecorator = false;
1490 if( position.x < 0.f )
1492 offset.x = -position.x;
1493 mEventData->mScrollPosition.x += offset.x;
1494 updateDecorator = true;
1496 else if( position.x > mControlSize.width )
1498 offset.x = mControlSize.width - position.x;
1499 mEventData->mScrollPosition.x += offset.x;
1500 updateDecorator = true;
1503 if( updateDecorator && mEventData->mDecorator )
1505 mEventData->mDecorator->UpdatePositions( offset );
1508 // TODO : calculate the vertical scroll.
1511 void Controller::Impl::ScrollTextToMatchCursor()
1513 // Get the current cursor position in decorator coords.
1514 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1516 // Calculate the new cursor position.
1517 CursorInfo cursorInfo;
1518 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1521 // Calculate the offset to match the cursor position before the character was deleted.
1522 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1524 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1525 bool updateCursorPosition = true;
1527 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1528 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1530 if( updateCursorPosition )
1532 // Sets the cursor position.
1533 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1536 cursorInfo.primaryCursorHeight,
1537 cursorInfo.lineHeight );
1539 // Sets the grab handle position.
1540 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1543 cursorInfo.lineHeight );
1545 if( cursorInfo.isSecondaryCursor )
1547 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1548 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1549 cursorInfo.secondaryPosition.x + offset.x,
1550 cursorInfo.secondaryPosition.y + offset.y,
1551 cursorInfo.secondaryCursorHeight,
1552 cursorInfo.lineHeight );
1553 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1557 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1562 void Controller::Impl::RequestRelayout()
1564 mControlInterface.RequestTextRelayout();
1569 } // namespace Toolkit