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 );
187 OnSelectEvent( *iter );
190 case Event::SELECT_ALL:
200 // The cursor must also be repositioned after inserts into the model
201 if( mEventData->mUpdateCursorPosition )
203 // Updates the cursor position and scrolls the text to make it visible.
205 UpdateCursorPosition();
207 if( mEventData->mScrollAfterUpdatePosition )
209 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
211 ScrollToMakePositionVisible( primaryCursorPosition );
212 mEventData->mScrollAfterUpdatePosition = false;
215 mEventData->mDecoratorUpdated = true;
216 mEventData->mUpdateCursorPosition = false;
218 else if( mEventData->mScrollAfterDelete )
220 ScrollTextToMatchCursor();
221 mEventData->mDecoratorUpdated = true;
222 mEventData->mScrollAfterDelete = false;
226 bool leftScroll = false;
227 bool rightScroll = false;
229 if( mEventData->mUpdateLeftSelectionPosition )
231 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
233 if( mEventData->mScrollAfterUpdatePosition )
235 const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE );
237 ScrollToMakePositionVisible( leftHandlePosition );
241 mEventData->mDecoratorUpdated = true;
242 mEventData->mUpdateLeftSelectionPosition = false;
245 if( mEventData->mUpdateRightSelectionPosition )
247 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
249 if( mEventData->mScrollAfterUpdatePosition )
251 const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE );
253 ScrollToMakePositionVisible( rightHandlePosition );
257 mEventData->mDecoratorUpdated = true;
258 mEventData->mUpdateRightSelectionPosition = false;
261 if( leftScroll || rightScroll )
263 mEventData->mScrollAfterUpdatePosition = false;
267 mEventData->mEventQueue.clear();
269 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
271 const bool decoratorUpdated = mEventData->mDecoratorUpdated;
272 mEventData->mDecoratorUpdated = false;
274 return decoratorUpdated;
277 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
279 // Calculate the operations to be done.
280 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
282 Vector<Character>& utf32Characters = mLogicalModel->mText;
284 const Length numberOfCharacters = utf32Characters.Count();
286 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
287 if( GET_LINE_BREAKS & operations )
289 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
290 // calculate the bidirectional info for each 'paragraph'.
291 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
292 // is not shaped together).
293 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
295 SetLineBreakInfo( utf32Characters,
299 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
300 if( GET_WORD_BREAKS & operations )
302 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
303 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
305 SetWordBreakInfo( utf32Characters,
309 const bool getScripts = GET_SCRIPTS & operations;
310 const bool validateFonts = VALIDATE_FONTS & operations;
312 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
313 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
315 if( getScripts || validateFonts )
317 // Validates the fonts assigned by the application or assigns default ones.
318 // It makes sure all the characters are going to be rendered by the correct font.
319 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
323 // Retrieves the scripts used in the text.
324 multilanguageSupport.SetScripts( utf32Characters,
331 if( 0u == validFonts.Count() )
333 // Copy the requested font defaults received via the property system.
334 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
335 GetDefaultFonts( validFonts, numberOfCharacters );
338 // Validates the fonts. If there is a character with no assigned font it sets a default one.
339 // After this call, fonts are validated.
340 multilanguageSupport.ValidateFonts( utf32Characters,
346 Vector<Character> mirroredUtf32Characters;
347 bool textMirrored = false;
348 if( BIDI_INFO & operations )
350 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
351 // bidirectional info.
353 Length numberOfParagraphs = 0u;
355 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
356 for( Length index = 0u; index < numberOfCharacters; ++index )
358 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
360 ++numberOfParagraphs;
364 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
365 bidirectionalInfo.Reserve( numberOfParagraphs );
367 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
368 SetBidirectionalInfo( utf32Characters,
373 if( 0u != bidirectionalInfo.Count() )
375 // This paragraph has right to left text. Some characters may need to be mirrored.
376 // TODO: consider if the mirrored string can be stored as well.
378 textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
380 // Only set the character directions if there is right to left characters.
381 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
382 directions.Resize( numberOfCharacters );
384 GetCharactersDirection( bidirectionalInfo,
389 // There is no right to left characters. Clear the directions vector.
390 mLogicalModel->mCharacterDirections.Clear();
395 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
396 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
397 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
398 if( SHAPE_TEXT & operations )
400 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
402 ShapeText( textToShape,
407 glyphsToCharactersMap,
408 charactersPerGlyph );
410 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
411 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
412 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
415 const Length numberOfGlyphs = glyphs.Count();
417 if( GET_GLYPH_METRICS & operations )
419 mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
423 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
428 fontRun.characterRun.characterIndex = 0;
429 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
430 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
431 fontRun.isDefault = true;
433 fonts.PushBack( fontRun );
437 void Controller::Impl::OnCursorKeyEvent( const Event& event )
439 if( NULL == mEventData )
441 // Nothing to do if there is no text input.
445 int keyCode = event.p1.mInt;
447 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
449 if( mEventData->mPrimaryCursorPosition > 0u )
451 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
454 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
456 if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
458 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
461 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
465 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
470 mEventData->mUpdateCursorPosition = true;
471 mEventData->mScrollAfterUpdatePosition = true;
474 void Controller::Impl::OnTapEvent( const Event& event )
476 if( NULL != mEventData )
478 const unsigned int tapCount = event.p1.mUint;
482 if( ! IsShowingPlaceholderText() )
484 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
485 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
487 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
492 mEventData->mPrimaryCursorPosition = 0u;
495 mEventData->mUpdateCursorPosition = true;
496 mEventData->mScrollAfterUpdatePosition = true;
501 void Controller::Impl::OnPanEvent( const Event& event )
503 if( NULL == mEventData )
505 // Nothing to do if there is no text input.
509 int state = event.p1.mInt;
511 if( Gesture::Started == state ||
512 Gesture::Continuing == state )
514 const Vector2& actualSize = mVisualModel->GetActualSize();
515 const Vector2 currentScroll = mEventData->mScrollPosition;
517 if( mEventData->mHorizontalScrollingEnabled )
519 const float displacementX = event.p2.mFloat;
520 mEventData->mScrollPosition.x += displacementX;
522 ClampHorizontalScroll( actualSize );
525 if( mEventData->mVerticalScrollingEnabled )
527 const float displacementY = event.p3.mFloat;
528 mEventData->mScrollPosition.y += displacementY;
530 ClampVerticalScroll( actualSize );
533 if( mEventData->mDecorator )
535 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
540 void Controller::Impl::OnHandleEvent( const Event& event )
542 if( NULL == mEventData )
544 // Nothing to do if there is no text input.
548 const unsigned int state = event.p1.mUint;
549 const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
551 if( HANDLE_PRESSED == state )
553 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
554 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
555 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
557 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
559 if( Event::GRAB_HANDLE_EVENT == event.type )
561 ChangeState ( EventData::GRAB_HANDLE_PANNING );
563 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
565 mEventData->mPrimaryCursorPosition = handleNewPosition;
566 mEventData->mUpdateCursorPosition = true;
569 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
571 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
573 if( handleNewPosition != mEventData->mLeftSelectionPosition )
575 mEventData->mLeftSelectionPosition = handleNewPosition;
577 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
578 mEventData->mRightSelectionPosition );
580 mEventData->mUpdateLeftSelectionPosition = true;
583 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
585 ChangeState ( EventData::SELECTION_HANDLE_PANNING );
587 if( handleNewPosition != mEventData->mRightSelectionPosition )
589 mEventData->mRightSelectionPosition = handleNewPosition;
591 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
592 mEventData->mRightSelectionPosition );
594 mEventData->mUpdateRightSelectionPosition = true;
597 } // end ( HANDLE_PRESSED == state )
598 else if( ( HANDLE_RELEASED == state ) ||
599 handleStopScrolling )
601 CharacterIndex handlePosition = 0u;
602 if( handleStopScrolling )
604 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
605 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
606 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
608 handlePosition = GetClosestCursorIndex( xPosition, yPosition );
611 if( Event::GRAB_HANDLE_EVENT == event.type )
613 mEventData->mUpdateCursorPosition = true;
615 ChangeState( EventData::EDITING_WITH_POPUP );
617 if( handleStopScrolling )
619 mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
620 mEventData->mPrimaryCursorPosition = handlePosition;
623 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
625 ChangeState( EventData::SELECTING );
627 if( handleStopScrolling )
629 mEventData->mUpdateLeftSelectionPosition = mEventData->mLeftSelectionPosition != handlePosition;
630 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
631 mEventData->mLeftSelectionPosition = handlePosition;
633 if( mEventData->mUpdateLeftSelectionPosition )
635 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
636 mEventData->mRightSelectionPosition );
640 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
642 ChangeState( EventData::SELECTING );
644 if( handleStopScrolling )
646 mEventData->mUpdateRightSelectionPosition = mEventData->mRightSelectionPosition != handlePosition;
647 mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
648 mEventData->mRightSelectionPosition = handlePosition;
650 if( mEventData->mUpdateRightSelectionPosition )
652 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
653 mEventData->mRightSelectionPosition );
658 mEventData->mDecoratorUpdated = true;
659 } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
660 else if( HANDLE_SCROLLING == state )
662 const float xSpeed = event.p2.mFloat;
663 const Vector2& actualSize = mVisualModel->GetActualSize();
665 mEventData->mScrollPosition.x += xSpeed;
667 ClampHorizontalScroll( actualSize );
669 const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
670 const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
672 if( Event::GRAB_HANDLE_EVENT == event.type )
674 ChangeState( EventData::GRAB_HANDLE_PANNING );
676 else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
678 // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
679 // Think if something can be done to save power.
681 ChangeState( EventData::SELECTION_HANDLE_PANNING );
683 const Vector2& position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
685 // Get the new handle position.
686 // The selection handle's position is in decorator coords. Need to transforms to text coords.
687 const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
688 position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
690 if( leftSelectionHandleEvent )
692 mEventData->mUpdateLeftSelectionPosition = handlePosition != mEventData->mLeftSelectionPosition;
693 mEventData->mLeftSelectionPosition = handlePosition;
697 mEventData->mUpdateRightSelectionPosition = handlePosition != mEventData->mRightSelectionPosition;
698 mEventData->mRightSelectionPosition = handlePosition;
701 if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
703 RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
704 mEventData->mRightSelectionPosition );
707 mEventData->mDecoratorUpdated = true;
708 } // end ( HANDLE_SCROLLING == state )
711 void Controller::Impl::OnSelectEvent( const Event& event )
713 if( NULL == mEventData )
715 // Nothing to do if there is no text.
719 if( mEventData->mSelectionEnabled )
721 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
722 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
723 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
725 RepositionSelectionHandles( xPosition,
728 mEventData->mScrollAfterUpdatePosition = true;
729 mEventData->mUpdateLeftSelectionPosition = true;
730 mEventData->mUpdateRightSelectionPosition = true;
734 void Controller::Impl::OnSelectAllEvent()
736 if( NULL == mEventData )
738 // Nothing to do if there is no text.
742 if( mEventData->mSelectionEnabled )
744 RepositionSelectionHandles( 0u,
745 mLogicalModel->mText.Count() );
747 mEventData->mScrollAfterUpdatePosition = true;
748 mEventData->mUpdateLeftSelectionPosition = true;
749 mEventData->mUpdateRightSelectionPosition = true;
753 void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
755 if( selectionStart == selectionEnd )
757 // Nothing to select if handles are in the same place.
761 mEventData->mDecorator->ClearHighlights();
763 mEventData->mLeftSelectionPosition = selectionStart;
764 mEventData->mRightSelectionPosition = selectionEnd;
766 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
767 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
768 const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
769 const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
771 // TODO: Better algorithm to create the highlight box.
774 const Vector<LineRun>& lines = mVisualModel->mLines;
775 const LineRun& firstLine = *lines.Begin();
776 const float height = firstLine.ascender + -firstLine.descender;
778 const bool indicesSwapped = ( selectionStart > selectionEnd );
781 std::swap( selectionStart, selectionEnd );
784 GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
785 GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) ) - 1u;
787 mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
789 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
791 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
793 const GlyphInfo& glyph = *( glyphsBuffer + index );
794 const Vector2& position = *( positionsBuffer + index );
796 const float xPosition = position.x - glyph.xBearing + offset.x;
797 mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
800 CursorInfo primaryCursorInfo;
801 GetCursorPosition( mEventData->mLeftSelectionPosition,
804 CursorInfo secondaryCursorInfo;
805 GetCursorPosition( mEventData->mRightSelectionPosition,
806 secondaryCursorInfo );
808 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
809 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
811 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
813 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
815 // Set the flag to update the decorator.
816 mEventData->mDecoratorUpdated = true;
819 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
821 if( NULL == mEventData )
823 // Nothing to do if there is no text input.
827 if( IsShowingPlaceholderText() )
829 // Nothing to do if there is the place-holder text.
833 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
834 const Length numberOfLines = mVisualModel->mLines.Count();
835 if( 0 == numberOfGlyphs ||
838 // Nothing to do if there is no text.
842 // Find which word was selected
843 CharacterIndex selectionStart( 0 );
844 CharacterIndex selectionEnd( 0 );
845 FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
846 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
848 if( selectionStart == selectionEnd )
850 ChangeState( EventData::EDITING );
851 // Nothing to select. i.e. a white space, out of bounds
855 RepositionSelectionHandles( selectionStart, selectionEnd );
858 void Controller::Impl::ChangeState( EventData::State newState )
860 if( NULL == mEventData )
862 // Nothing to do if there is no text input.
866 if( mEventData->mState != newState )
868 mEventData->mState = newState;
870 if( EventData::INACTIVE == mEventData->mState )
872 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
873 mEventData->mDecorator->StopCursorBlink();
874 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
875 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
876 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
877 mEventData->mDecorator->SetPopupActive( false );
878 mEventData->mDecoratorUpdated = true;
880 else if ( EventData::SELECTING == mEventData->mState )
882 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
883 mEventData->mDecorator->StopCursorBlink();
884 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
885 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
886 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
887 if( mEventData->mGrabHandlePopupEnabled )
889 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
890 if ( !IsClipboardEmpty() )
892 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
895 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
896 mEventData->mDecorator->SetPopupActive( true );
898 mEventData->mDecoratorUpdated = true;
900 else if ( EventData::SELECTION_CHANGED == mEventData->mState )
902 if( mEventData->mGrabHandlePopupEnabled )
904 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
905 if ( !IsClipboardEmpty() )
907 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
909 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
910 mEventData->mDecorator->SetPopupActive( true );
912 mEventData->mDecoratorUpdated = true;
914 else if( EventData::EDITING == mEventData->mState )
916 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
917 if( mEventData->mCursorBlinkEnabled )
919 mEventData->mDecorator->StartCursorBlink();
921 // Grab handle is not shown until a tap is received whilst EDITING
922 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
923 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
924 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
925 if( mEventData->mGrabHandlePopupEnabled )
927 mEventData->mDecorator->SetPopupActive( false );
929 mEventData->mDecoratorUpdated = true;
931 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
933 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
934 if( mEventData->mCursorBlinkEnabled )
936 mEventData->mDecorator->StartCursorBlink();
938 if( mEventData->mSelectionEnabled )
940 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
941 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
945 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
947 if( mEventData->mGrabHandlePopupEnabled )
949 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
951 if ( !IsClipboardEmpty() )
953 buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
956 mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
957 mEventData->mDecorator->SetPopupActive( true );
959 mEventData->mDecoratorUpdated = true;
961 else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
963 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
964 mEventData->mDecorator->StopCursorBlink();
965 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
966 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
967 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
968 if( mEventData->mGrabHandlePopupEnabled )
970 mEventData->mDecorator->SetPopupActive( false );
972 mEventData->mDecoratorUpdated = true;
974 else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
976 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
977 if( mEventData->mCursorBlinkEnabled )
979 mEventData->mDecorator->StartCursorBlink();
981 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
982 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
983 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
984 if( mEventData->mGrabHandlePopupEnabled )
986 mEventData->mDecorator->SetPopupActive( false );
988 mEventData->mDecoratorUpdated = true;
993 LineIndex Controller::Impl::GetClosestLine( float y ) const
995 float totalHeight = 0.f;
996 LineIndex lineIndex = 0u;
998 const Vector<LineRun>& lines = mVisualModel->mLines;
999 for( LineIndex endLine = lines.Count();
1000 lineIndex < endLine;
1003 const LineRun& lineRun = lines[lineIndex];
1004 totalHeight += lineRun.ascender + -lineRun.descender;
1005 if( y < totalHeight )
1011 if( lineIndex == 0 )
1019 void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
1021 CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
1022 if( hitCharacter >= mLogicalModel->mText.Count() )
1024 // Selection out of bounds.
1028 startIndex = hitCharacter;
1029 endIndex = hitCharacter;
1031 if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
1033 // Find the start and end of the text
1034 for( startIndex = hitCharacter; startIndex > 0; --startIndex )
1036 Character charCode = mLogicalModel->mText[ startIndex-1 ];
1037 if( TextAbstraction::IsWhiteSpace( charCode ) )
1042 const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
1043 for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
1045 Character charCode = mLogicalModel->mText[ endIndex ];
1046 if( TextAbstraction::IsWhiteSpace( charCode ) )
1054 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
1057 if( NULL == mEventData )
1059 // Nothing to do if there is no text input.
1063 CharacterIndex logicalIndex = 0u;
1065 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
1066 const Length numberOfLines = mVisualModel->mLines.Count();
1067 if( 0 == numberOfGlyphs ||
1068 0 == numberOfLines )
1070 return logicalIndex;
1073 // Find which line is closest
1074 const LineIndex lineIndex = GetClosestLine( visualY );
1075 const LineRun& line = mVisualModel->mLines[lineIndex];
1077 // Get the positions of the glyphs.
1078 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
1079 const Vector2* const positionsBuffer = positions.Begin();
1081 // Get the visual to logical conversion tables.
1082 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
1083 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
1085 // Get the character to glyph conversion table.
1086 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1088 // Get the glyphs per character table.
1089 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
1091 // If the vector is void, there is no right to left characters.
1092 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
1094 const CharacterIndex startCharacter = line.characterRun.characterIndex;
1095 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
1096 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
1098 // Whether there is a hit on a glyph.
1099 bool matched = false;
1101 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
1102 CharacterIndex visualIndex = startCharacter;
1103 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
1105 // The character in logical order.
1106 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
1108 // The first glyph for that character in logical order.
1109 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
1111 // The number of glyphs for that character
1112 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
1114 // Get the metrics for the group of glyphs.
1115 GlyphMetrics glyphMetrics;
1116 GetGlyphsMetrics( glyphLogicalOrderIndex,
1122 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
1124 // Find the mid-point of the area containing the glyph
1125 const float glyphCenter = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
1127 if( visualX < glyphCenter )
1134 // Return the logical position of the cursor in characters.
1138 visualIndex = endCharacter;
1141 logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
1142 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
1143 return logicalIndex;
1146 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
1147 CursorInfo& cursorInfo )
1149 // TODO: Check for multiline with \n, etc...
1151 // Check if the logical position is the first or the last one of the text.
1152 const bool isFirstPosition = 0u == logical;
1153 const bool isLastPosition = mLogicalModel->mText.Count() == logical;
1155 if( isFirstPosition && isLastPosition )
1157 // There is zero characters. Get the default font.
1159 FontId defaultFontId = 0u;
1160 if( NULL == mFontDefaults )
1162 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1167 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1170 Text::FontMetrics fontMetrics;
1171 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1173 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
1174 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1176 cursorInfo.primaryPosition.x = 0.f;
1177 cursorInfo.primaryPosition.y = 0.f;
1179 // Nothing else to do.
1183 // Get the previous logical index.
1184 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
1186 // Decrease the logical index if it's the last one.
1187 if( isLastPosition )
1192 // Get the direction of the character and the previous one.
1193 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
1195 CharacterDirection isCurrentRightToLeft = false;
1196 CharacterDirection isPreviousRightToLeft = false;
1197 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
1199 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
1200 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
1203 // Get the line where the character is laid-out.
1204 const LineRun* modelLines = mVisualModel->mLines.Begin();
1206 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
1207 const LineRun& line = *( modelLines + lineIndex );
1209 // Get the paragraph's direction.
1210 const CharacterDirection isRightToLeftParagraph = line.direction;
1212 // Check whether there is an alternative position:
1214 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
1215 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
1217 // Set the line height.
1218 cursorInfo.lineHeight = line.ascender + -line.descender;
1220 // Convert the cursor position into the glyph position.
1221 CharacterIndex characterIndex = logical;
1222 if( cursorInfo.isSecondaryCursor &&
1223 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
1225 characterIndex = previousLogical;
1228 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1229 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1230 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
1232 // Get the metrics for the group of glyphs.
1233 GlyphMetrics glyphMetrics;
1234 GetGlyphsMetrics( currentGlyphIndex,
1240 float interGlyphAdvance = 0.f;
1241 if( !isLastPosition &&
1242 ( numberOfCharacters > 1u ) )
1244 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
1245 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
1248 // Get the glyph position and x bearing.
1249 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
1251 // Set the cursor's height.
1252 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
1254 // Set the position.
1255 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
1256 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1258 if( isLastPosition )
1260 // The position of the cursor after the last character needs special
1261 // care depending on its direction and the direction of the paragraph.
1263 if( cursorInfo.isSecondaryCursor )
1265 // Need to find the first character after the last character with the paragraph's direction.
1266 // i.e l0 l1 l2 r0 r1 should find r0.
1268 // TODO: check for more than one line!
1269 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1270 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
1272 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1273 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1275 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
1277 // Get the metrics for the group of glyphs.
1278 GlyphMetrics glyphMetrics;
1279 GetGlyphsMetrics( glyphIndex,
1285 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1287 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1291 if( !isCurrentRightToLeft )
1293 cursorInfo.primaryPosition.x += glyphMetrics.advance;
1297 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1302 // Set the alternative cursor position.
1303 if( cursorInfo.isSecondaryCursor )
1305 // Convert the cursor position into the glyph position.
1306 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1307 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1308 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1310 // Get the glyph position.
1311 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1313 // Get the metrics for the group of glyphs.
1314 GlyphMetrics glyphMetrics;
1315 GetGlyphsMetrics( previousGlyphIndex,
1321 // Set the cursor position and height.
1322 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1323 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1325 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1327 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1329 // Update the primary cursor height as well.
1330 cursorInfo.primaryCursorHeight *= 0.5f;
1334 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1336 if( NULL == mEventData )
1338 // Nothing to do if there is no text input.
1342 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1344 const Script script = mLogicalModel->GetScript( index );
1345 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1346 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1348 Length numberOfCharacters = 0u;
1349 if( TextAbstraction::LATIN == script )
1351 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1352 numberOfCharacters = 1u;
1356 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1357 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1359 while( 0u == numberOfCharacters )
1361 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1366 if( index < mEventData->mPrimaryCursorPosition )
1368 cursorIndex -= numberOfCharacters;
1372 cursorIndex += numberOfCharacters;
1378 void Controller::Impl::UpdateCursorPosition()
1380 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1381 if( NULL == mEventData )
1383 // Nothing to do if there is no text input.
1384 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1388 if( IsShowingPlaceholderText() )
1390 // Do not want to use the place-holder text to set the cursor position.
1392 // Use the line's height of the font's family set to set the cursor's size.
1393 // If there is no font's family set, use the default font.
1394 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1396 float lineHeight = 0.f;
1398 FontId defaultFontId = 0u;
1399 if( NULL == mFontDefaults )
1401 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1406 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1409 Text::FontMetrics fontMetrics;
1410 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1412 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1415 Vector2 cursorPosition;
1417 switch( mLayoutEngine.GetHorizontalAlignment() )
1419 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1421 cursorPosition.x = 1.f;
1424 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1426 cursorPosition.x = floor( 0.5f * mControlSize.width );
1429 case LayoutEngine::HORIZONTAL_ALIGN_END:
1431 cursorPosition.x = mControlSize.width;
1436 switch( mLayoutEngine.GetVerticalAlignment() )
1438 case LayoutEngine::VERTICAL_ALIGN_TOP:
1440 cursorPosition.y = 0.f;
1443 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1445 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1448 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1450 cursorPosition.y = mControlSize.height - lineHeight;
1455 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1463 CursorInfo cursorInfo;
1464 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1467 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1468 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1470 // Sets the cursor position.
1471 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1474 cursorInfo.primaryCursorHeight,
1475 cursorInfo.lineHeight );
1476 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1478 // Sets the grab handle position.
1479 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1482 cursorInfo.lineHeight );
1484 if( cursorInfo.isSecondaryCursor )
1486 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1487 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1488 cursorInfo.secondaryPosition.x + offset.x,
1489 cursorInfo.secondaryPosition.y + offset.y,
1490 cursorInfo.secondaryCursorHeight,
1491 cursorInfo.lineHeight );
1492 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1496 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1499 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1502 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1504 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1505 ( RIGHT_SELECTION_HANDLE != handleType ) )
1510 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1511 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1513 CursorInfo cursorInfo;
1514 GetCursorPosition( index,
1517 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1518 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1520 // Sets the grab handle position.
1521 mEventData->mDecorator->SetPosition( handleType,
1524 cursorInfo.lineHeight );
1527 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1529 // Clamp between -space & 0 (and the text alignment).
1530 if( actualSize.width > mControlSize.width )
1532 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1533 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1534 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1536 mEventData->mDecoratorUpdated = true;
1540 mEventData->mScrollPosition.x = 0.f;
1544 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1546 // Clamp between -space & 0 (and the text alignment).
1547 if( actualSize.height > mControlSize.height )
1549 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1550 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1551 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1553 mEventData->mDecoratorUpdated = true;
1557 mEventData->mScrollPosition.y = 0.f;
1561 void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
1564 bool updateDecorator = false;
1565 if( position.x < 0.f )
1567 offset.x = -position.x;
1568 mEventData->mScrollPosition.x += offset.x;
1569 updateDecorator = true;
1571 else if( position.x > mControlSize.width )
1573 offset.x = mControlSize.width - position.x;
1574 mEventData->mScrollPosition.x += offset.x;
1575 updateDecorator = true;
1578 if( updateDecorator && mEventData->mDecorator )
1580 mEventData->mDecorator->UpdatePositions( offset );
1583 // TODO : calculate the vertical scroll.
1586 void Controller::Impl::ScrollTextToMatchCursor()
1588 // Get the current cursor position in decorator coords.
1589 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1591 // Calculate the new cursor position.
1592 CursorInfo cursorInfo;
1593 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1596 // Calculate the offset to match the cursor position before the character was deleted.
1597 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1599 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1600 bool updateCursorPosition = true;
1602 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1603 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1605 if( updateCursorPosition )
1607 // Sets the cursor position.
1608 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1611 cursorInfo.primaryCursorHeight,
1612 cursorInfo.lineHeight );
1614 // Sets the grab handle position.
1615 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1618 cursorInfo.lineHeight );
1620 if( cursorInfo.isSecondaryCursor )
1622 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1623 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1624 cursorInfo.secondaryPosition.x + offset.x,
1625 cursorInfo.secondaryPosition.y + offset.y,
1626 cursorInfo.secondaryCursorHeight,
1627 cursorInfo.lineHeight );
1628 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1632 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1637 void Controller::Impl::RequestRelayout()
1639 mControlInterface.RequestTextRelayout();
1644 } // namespace Toolkit