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>
25 #include <dali-toolkit/internal/text/bidirectional-support.h>
26 #include <dali-toolkit/internal/text/character-set-conversion.h>
27 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
28 #include <dali-toolkit/internal/text/multi-language-support.h>
29 #include <dali-toolkit/internal/text/script-run.h>
30 #include <dali-toolkit/internal/text/segmentation.h>
31 #include <dali-toolkit/internal/text/shaper.h>
32 #include <dali-toolkit/internal/text/text-io.h>
33 #include <dali-toolkit/internal/text/text-view.h>
39 * @brief Some characters can be shaped in more than one glyph.
40 * This struct is used to retrieve metrics from these group of glyphs.
54 float fontHeight; ///< The font's height of that glyphs.
55 float advance; ///< The sum of all the advances of all the glyphs.
56 float ascender; ///< The font's ascender.
57 float xBearing; ///< The x bearing of the first glyph.
60 const std::string EMPTY_STRING("");
74 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
76 * @param[in] glyphIndex The index to the first glyph.
77 * @param[in] numberOfGlyphs The number of glyphs.
78 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
82 void GetGlyphsMetrics( GlyphIndex glyphIndex,
83 Length numberOfGlyphs,
84 GlyphMetrics& glyphMetrics,
85 VisualModelPtr visualModel,
86 TextAbstraction::FontClient& fontClient )
88 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
90 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
92 Text::FontMetrics fontMetrics;
93 fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics );
95 glyphMetrics.fontHeight = fontMetrics.height;
96 glyphMetrics.advance = firstGlyph.advance;
97 glyphMetrics.ascender = fontMetrics.ascender;
98 glyphMetrics.xBearing = firstGlyph.xBearing;
100 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
102 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
104 glyphMetrics.advance += glyphInfo.advance;
108 EventData::EventData( DecoratorPtr decorator )
109 : mDecorator( decorator ),
110 mPlaceholderTextActive(),
111 mPlaceholderTextInactive(),
112 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
116 mPrimaryCursorPosition( 0u ),
117 mLeftSelectionPosition( 0u ),
118 mRightSelectionPosition( 0u ),
119 mPreEditStartPosition( 0u ),
120 mPreEditLength( 0u ),
121 mIsShowingPlaceholderText( false ),
122 mPreEditFlag( false ),
123 mDecoratorUpdated( false ),
124 mCursorBlinkEnabled( true ),
125 mGrabHandleEnabled( true ),
126 mGrabHandlePopupEnabled( false ),
127 mSelectionEnabled( false ),
128 mHorizontalScrollingEnabled( true ),
129 mVerticalScrollingEnabled( false ),
130 mUpdateCursorPosition( false ),
131 mUpdateLeftSelectionPosition( false ),
132 mUpdateRightSelectionPosition( false ),
133 mScrollAfterUpdateCursorPosition( false )
136 EventData::~EventData()
139 bool Controller::Impl::ProcessInputEvents()
141 if( NULL == mEventData )
143 // Nothing to do if there is no text input.
147 mEventData->mDecoratorUpdated = false;
149 if( mEventData->mDecorator )
151 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
152 iter != mEventData->mEventQueue.end();
157 case Event::KEYBOARD_FOCUS_GAIN_EVENT:
159 OnKeyboardFocus( true );
162 case Event::KEYBOARD_FOCUS_LOST_EVENT:
164 OnKeyboardFocus( false );
167 case Event::CURSOR_KEY_EVENT:
169 OnCursorKeyEvent( *iter );
172 case Event::TAP_EVENT:
177 case Event::PAN_EVENT:
182 case Event::GRAB_HANDLE_EVENT:
183 case Event::LEFT_SELECTION_HANDLE_EVENT:
184 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
186 OnHandleEvent( *iter );
193 // The cursor must also be repositioned after inserts into the model
194 if( mEventData->mUpdateCursorPosition )
196 // Updates the cursor position and scrolls the text to make it visible.
198 UpdateCursorPosition();
200 if( mEventData->mScrollAfterUpdateCursorPosition )
202 ScrollToMakeCursorVisible();
203 mEventData->mScrollAfterUpdateCursorPosition = false;
206 mEventData->mDecoratorUpdated = true;
207 mEventData->mUpdateCursorPosition = false;
209 else if( mEventData->mUpdateLeftSelectionPosition )
211 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
213 if( mEventData->mScrollAfterUpdateCursorPosition )
215 ScrollToMakeCursorVisible();
216 mEventData->mScrollAfterUpdateCursorPosition = false;
219 mEventData->mDecoratorUpdated = true;
220 mEventData->mUpdateLeftSelectionPosition = false;
222 else if( mEventData->mUpdateRightSelectionPosition )
224 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
226 if( mEventData->mScrollAfterUpdateCursorPosition )
228 ScrollToMakeCursorVisible();
229 mEventData->mScrollAfterUpdateCursorPosition = false;
232 mEventData->mDecoratorUpdated = true;
233 mEventData->mUpdateRightSelectionPosition = false;
236 mEventData->mEventQueue.clear();
238 return mEventData->mDecoratorUpdated;
241 void Controller::Impl::ReplaceTextWithPlaceholder()
243 DALI_ASSERT_DEBUG( mEventData && "No placeholder text available" );
249 // Disable handles when showing place-holder text
250 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
251 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
252 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
254 const char* text( NULL );
257 if( EventData::INACTIVE != mEventData->mState &&
258 0u != mEventData->mPlaceholderTextActive.c_str() )
260 text = mEventData->mPlaceholderTextActive.c_str();
261 size = mEventData->mPlaceholderTextActive.size();
266 text = mEventData->mPlaceholderTextInactive.c_str();
267 size = mEventData->mPlaceholderTextInactive.size();
271 mLogicalModel->mText.Clear();
272 mLogicalModel->mScriptRuns.Clear();
273 mLogicalModel->mFontRuns.Clear();
274 mLogicalModel->mLineBreakInfo.Clear();
275 mLogicalModel->mWordBreakInfo.Clear();
276 mLogicalModel->mBidirectionalParagraphInfo.Clear();
277 mLogicalModel->mCharacterDirections.Clear();
278 mLogicalModel->mBidirectionalLineInfo.Clear();
279 mLogicalModel->mLogicalToVisualMap.Clear();
280 mLogicalModel->mVisualToLogicalMap.Clear();
281 mVisualModel->mGlyphs.Clear();
282 mVisualModel->mGlyphsToCharacters.Clear();
283 mVisualModel->mCharactersToGlyph.Clear();
284 mVisualModel->mCharactersPerGlyph.Clear();
285 mVisualModel->mGlyphsPerCharacter.Clear();
286 mVisualModel->mGlyphPositions.Clear();
287 mVisualModel->mLines.Clear();
288 mVisualModel->ClearCaches();
289 mVisualModel->SetTextColor( mEventData->mPlaceholderTextColor );
291 // Convert text into UTF-32
292 Vector<Character>& utf32Characters = mLogicalModel->mText;
293 utf32Characters.Resize( size );
295 // This is a bit horrible but std::string returns a (signed) char*
296 const uint8_t* utf8 = reinterpret_cast<const uint8_t*>( text );
298 // Transform a text array encoded in utf8 into an array encoded in utf32.
299 // It returns the actual number of characters.
300 Length characterCount = Utf8ToUtf32( utf8, size, utf32Characters.Begin() );
301 utf32Characters.Resize( characterCount );
303 // Reset the cursor position
304 mEventData->mPrimaryCursorPosition = 0;
306 // The natural size needs to be re-calculated.
307 mRecalculateNaturalSize = true;
309 // Apply modifications to the model
310 mOperationsPending = ALL_OPERATIONS;
311 UpdateModel( ALL_OPERATIONS );
312 mOperationsPending = static_cast<OperationsMask>( LAYOUT |
318 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
320 // Calculate the operations to be done.
321 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
323 Vector<Character>& utf32Characters = mLogicalModel->mText;
325 const Length numberOfCharacters = mLogicalModel->GetNumberOfCharacters();
327 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
328 if( GET_LINE_BREAKS & operations )
330 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
331 // calculate the bidirectional info for each 'paragraph'.
332 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
333 // is not shaped together).
334 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
336 SetLineBreakInfo( utf32Characters,
340 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
341 if( GET_WORD_BREAKS & operations )
343 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
344 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
346 SetWordBreakInfo( utf32Characters,
350 const bool getScripts = GET_SCRIPTS & operations;
351 const bool validateFonts = VALIDATE_FONTS & operations;
353 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
354 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
356 if( getScripts || validateFonts )
358 // Validates the fonts assigned by the application or assigns default ones.
359 // It makes sure all the characters are going to be rendered by the correct font.
360 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
364 // Retrieves the scripts used in the text.
365 multilanguageSupport.SetScripts( utf32Characters,
372 if( 0u == validFonts.Count() )
374 // Copy the requested font defaults received via the property system.
375 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
376 GetDefaultFonts( validFonts, numberOfCharacters );
379 // Validates the fonts. If there is a character with no assigned font it sets a default one.
380 // After this call, fonts are validated.
381 multilanguageSupport.ValidateFonts( utf32Characters,
387 Vector<Character> mirroredUtf32Characters;
388 bool textMirrored = false;
389 if( BIDI_INFO & operations )
391 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
392 // bidirectional info.
394 Length numberOfParagraphs = 0u;
396 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
397 for( Length index = 0u; index < numberOfCharacters; ++index )
399 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
401 ++numberOfParagraphs;
405 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
406 bidirectionalInfo.Reserve( numberOfParagraphs );
408 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
409 SetBidirectionalInfo( utf32Characters,
414 if( 0u != bidirectionalInfo.Count() )
416 // This paragraph has right to left text. Some characters may need to be mirrored.
417 // TODO: consider if the mirrored string can be stored as well.
419 textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
421 // Only set the character directions if there is right to left characters.
422 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
423 directions.Resize( numberOfCharacters );
425 GetCharactersDirection( bidirectionalInfo,
430 // There is no right to left characters. Clear the directions vector.
431 mLogicalModel->mCharacterDirections.Clear();
436 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
437 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
438 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
439 if( SHAPE_TEXT & operations )
441 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
443 ShapeText( textToShape,
448 glyphsToCharactersMap,
449 charactersPerGlyph );
451 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
452 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
453 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
456 const Length numberOfGlyphs = glyphs.Count();
458 if( GET_GLYPH_METRICS & operations )
460 mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
464 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
469 fontRun.characterRun.characterIndex = 0;
470 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
471 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
472 fontRun.isDefault = true;
474 fonts.PushBack( fontRun );
478 void Controller::Impl::OnKeyboardFocus( bool hasFocus )
480 if( NULL == mEventData )
482 // Nothing to do if there is no text input.
488 ChangeState( EventData::INACTIVE );
492 ChangeState( EventData::EDITING );
496 void Controller::Impl::OnCursorKeyEvent( const Event& event )
498 if( NULL == mEventData )
500 // Nothing to do if there is no text input.
504 int keyCode = event.p1.mInt;
506 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
508 if( mEventData->mPrimaryCursorPosition > 0u )
510 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
513 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
515 if( mLogicalModel->GetNumberOfCharacters() > mEventData->mPrimaryCursorPosition )
517 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
520 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
524 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
529 mEventData->mUpdateCursorPosition = true;
530 mEventData->mScrollAfterUpdateCursorPosition = true;
533 void Controller::Impl::OnTapEvent( const Event& event )
535 if( NULL == mEventData )
537 // Nothing to do if there is no text input.
541 const unsigned int tapCount = event.p1.mUint;
545 // Grab handle is not shown until a tap is received whilst EDITING
546 if( EventData::EDITING == mEventData->mState &&
547 !IsShowingPlaceholderText() )
549 if( mEventData->mGrabHandleEnabled )
551 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
553 mEventData->mDecorator->SetPopupActive( false );
556 ChangeState( EventData::EDITING );
558 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
559 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
561 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
564 mEventData->mUpdateCursorPosition = true;
565 mEventData->mScrollAfterUpdateCursorPosition = true;
567 else if( mEventData->mSelectionEnabled &&
570 ChangeState( EventData::SELECTING );
572 RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat );
576 void Controller::Impl::OnPanEvent( const Event& event )
578 if( NULL == mEventData )
580 // Nothing to do if there is no text input.
584 int state = event.p1.mInt;
586 if( Gesture::Started == state ||
587 Gesture::Continuing == state )
589 const Vector2& actualSize = mVisualModel->GetActualSize();
590 const Vector2 currentScroll = mEventData->mScrollPosition;
592 if( mEventData->mHorizontalScrollingEnabled )
594 const float displacementX = event.p2.mFloat;
595 mEventData->mScrollPosition.x += displacementX;
597 ClampHorizontalScroll( actualSize );
600 if( mEventData->mVerticalScrollingEnabled )
602 const float displacementY = event.p3.mFloat;
603 mEventData->mScrollPosition.y += displacementY;
605 ClampVerticalScroll( actualSize );
608 if( mEventData->mDecorator )
610 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
615 void Controller::Impl::OnHandleEvent( const Event& event )
617 if( NULL == mEventData )
619 // Nothing to do if there is no text input.
623 const unsigned int state = event.p1.mUint;
625 if( HANDLE_PRESSED == state )
627 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
628 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
629 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
631 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
633 if( Event::GRAB_HANDLE_EVENT == event.type )
635 ChangeState ( EventData::EDITING );
637 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
639 mEventData->mPrimaryCursorPosition = handleNewPosition;
640 mEventData->mUpdateCursorPosition = true;
643 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
645 if( handleNewPosition != mEventData->mLeftSelectionPosition )
647 mEventData->mLeftSelectionPosition = handleNewPosition;
648 mEventData->mUpdateLeftSelectionPosition = true;
651 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
653 if( handleNewPosition != mEventData->mRightSelectionPosition )
655 mEventData->mRightSelectionPosition = handleNewPosition;
656 mEventData->mUpdateRightSelectionPosition = true;
660 else if( ( HANDLE_RELEASED == state ) ||
661 ( HANDLE_STOP_SCROLLING == state ) )
663 if( mEventData->mGrabHandlePopupEnabled )
665 ChangeState( EventData::EDITING_WITH_POPUP );
667 if( Event::GRAB_HANDLE_EVENT == event.type )
669 mEventData->mUpdateCursorPosition = true;
671 if( HANDLE_STOP_SCROLLING == state )
673 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
674 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
675 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
677 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
679 mEventData->mScrollAfterUpdateCursorPosition = true;
682 mEventData->mDecoratorUpdated = true;
684 else if( HANDLE_SCROLLING == state )
686 const float xSpeed = event.p2.mFloat;
687 const Vector2& actualSize = mVisualModel->GetActualSize();
689 mEventData->mScrollPosition.x += xSpeed;
691 ClampHorizontalScroll( actualSize );
693 mEventData->mDecoratorUpdated = true;
697 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
699 if( NULL == mEventData )
701 // Nothing to do if there is no text input.
705 // TODO - Find which word was selected
707 const Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
708 const Vector<Vector2>::SizeType glyphCount = glyphs.Count();
710 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
711 const Vector<Vector2>::SizeType positionCount = positions.Count();
713 // Guard against glyphs which did not fit inside the layout
714 const Vector<Vector2>::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount;
718 float primaryX = positions[0].x + mEventData->mScrollPosition.x;
719 float secondaryX = positions[count-1].x + glyphs[count-1].width + mEventData->mScrollPosition.x;
721 // TODO - multi-line selection
722 const Vector<LineRun>& lines = mVisualModel->mLines;
723 float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f;
725 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryX, mEventData->mScrollPosition.y, height );
726 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height );
728 mEventData->mDecorator->ClearHighlights();
729 mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y );
733 void Controller::Impl::ChangeState( EventData::State newState )
735 if( NULL == mEventData )
737 // Nothing to do if there is no text input.
741 if( mEventData->mState != newState )
743 // Show different placeholder when switching between active & inactive
744 bool updatePlaceholder( false );
745 if( IsShowingPlaceholderText() &&
746 ( EventData::INACTIVE == newState ||
747 EventData::INACTIVE == mEventData->mState ) )
749 updatePlaceholder = true;
752 mEventData->mState = newState;
754 if( updatePlaceholder )
756 ReplaceTextWithPlaceholder();
757 mEventData->mDecoratorUpdated = true;
760 if( EventData::INACTIVE == mEventData->mState )
762 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
763 mEventData->mDecorator->StopCursorBlink();
764 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
765 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
766 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
767 mEventData->mDecorator->SetPopupActive( false );
768 mEventData->mDecoratorUpdated = true;
770 else if ( EventData::SELECTING == mEventData->mState )
772 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
773 mEventData->mDecorator->StopCursorBlink();
774 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
775 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
776 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
777 mEventData->mDecoratorUpdated = true;
779 else if( EventData::EDITING == mEventData->mState )
781 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
782 if( mEventData->mCursorBlinkEnabled )
784 mEventData->mDecorator->StartCursorBlink();
786 // Grab handle is not shown until a tap is received whilst EDITING
787 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
788 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
789 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
790 mEventData->mDecoratorUpdated = true;
792 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
794 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
795 if( mEventData->mCursorBlinkEnabled )
797 mEventData->mDecorator->StartCursorBlink();
799 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
800 if( mEventData->mSelectionEnabled )
802 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
803 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
805 if( mEventData->mGrabHandlePopupEnabled )
807 mEventData->mDecorator->SetPopupActive( true );
809 mEventData->mDecoratorUpdated = true;
814 LineIndex Controller::Impl::GetClosestLine( float y ) const
816 float totalHeight = 0.f;
817 LineIndex lineIndex = 0u;
819 const Vector<LineRun>& lines = mVisualModel->mLines;
820 for( LineIndex endLine = lines.Count();
824 const LineRun& lineRun = lines[lineIndex];
825 totalHeight += lineRun.ascender + -lineRun.descender;
826 if( y < totalHeight )
835 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
838 if( NULL == mEventData )
840 // Nothing to do if there is no text input.
844 CharacterIndex logicalIndex = 0u;
846 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
847 const Length numberOfLines = mVisualModel->mLines.Count();
848 if( 0 == numberOfGlyphs ||
854 // Find which line is closest
855 const LineIndex lineIndex = GetClosestLine( visualY );
856 const LineRun& line = mVisualModel->mLines[lineIndex];
858 // Get the positions of the glyphs.
859 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
860 const Vector2* const positionsBuffer = positions.Begin();
862 // Get the visual to logical conversion tables.
863 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
864 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
866 // Get the character to glyph conversion table.
867 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
869 // Get the glyphs per character table.
870 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
872 // If the vector is void, there is no right to left characters.
873 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
875 const CharacterIndex startCharacter = line.characterRun.characterIndex;
876 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
877 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
879 // Whether there is a hit on a glyph.
880 bool matched = false;
882 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
883 CharacterIndex visualIndex = startCharacter;
884 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
886 // The character in logical order.
887 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
889 // The first glyph for that character in logical order.
890 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
892 // The number of glyphs for that character
893 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
895 // Get the metrics for the group of glyphs.
896 GlyphMetrics glyphMetrics;
897 GetGlyphsMetrics( glyphLogicalOrderIndex,
903 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
905 const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
907 if( visualX < glyphX )
914 // Return the logical position of the cursor in characters.
918 visualIndex = endCharacter;
921 return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
924 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
925 CursorInfo& cursorInfo )
927 // TODO: Check for multiline with \n, etc...
929 // Check if the logical position is the first or the last one of the text.
930 const bool isFirstPosition = 0u == logical;
931 const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
933 if( isFirstPosition && isLastPosition )
935 // There is zero characters. Get the default font.
937 FontId defaultFontId = 0u;
938 if( NULL == mFontDefaults )
940 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
945 defaultFontId = mFontDefaults->GetFontId( mFontClient );
948 Text::FontMetrics fontMetrics;
949 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
951 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
952 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
954 cursorInfo.primaryPosition.x = 0.f;
955 cursorInfo.primaryPosition.y = 0.f;
957 // Nothing else to do.
961 // Get the previous logical index.
962 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
964 // Decrease the logical index if it's the last one.
970 // Get the direction of the character and the previous one.
971 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
973 CharacterDirection isCurrentRightToLeft = false;
974 CharacterDirection isPreviousRightToLeft = false;
975 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
977 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
978 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
981 // Get the line where the character is laid-out.
982 const LineRun* modelLines = mVisualModel->mLines.Begin();
984 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
985 const LineRun& line = *( modelLines + lineIndex );
987 // Get the paragraph's direction.
988 const CharacterDirection isRightToLeftParagraph = line.direction;
990 // Check whether there is an alternative position:
992 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
993 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
995 // Set the line height.
996 cursorInfo.lineHeight = line.ascender + -line.descender;
998 // Convert the cursor position into the glyph position.
999 CharacterIndex characterIndex = logical;
1000 if( cursorInfo.isSecondaryCursor &&
1001 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
1003 characterIndex = previousLogical;
1006 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1007 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1008 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
1010 // Get the metrics for the group of glyphs.
1011 GlyphMetrics glyphMetrics;
1012 GetGlyphsMetrics( currentGlyphIndex,
1018 float interGlyphAdvance = 0.f;
1019 if( !isLastPosition &&
1020 ( numberOfCharacters > 1u ) )
1022 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
1023 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
1026 // Get the glyph position and x bearing.
1027 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
1029 // Set the cursor's height.
1030 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
1032 // Set the position.
1033 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
1034 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1036 if( isLastPosition )
1038 // The position of the cursor after the last character needs special
1039 // care depending on its direction and the direction of the paragraph.
1041 if( cursorInfo.isSecondaryCursor )
1043 // Need to find the first character after the last character with the paragraph's direction.
1044 // i.e l0 l1 l2 r0 r1 should find r0.
1046 // TODO: check for more than one line!
1047 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
1048 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
1050 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
1051 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
1053 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
1055 // Get the metrics for the group of glyphs.
1056 GlyphMetrics glyphMetrics;
1057 GetGlyphsMetrics( glyphIndex,
1063 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
1065 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
1069 if( !isCurrentRightToLeft )
1071 cursorInfo.primaryPosition.x += glyphMetrics.advance;
1075 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
1080 // Set the alternative cursor position.
1081 if( cursorInfo.isSecondaryCursor )
1083 // Convert the cursor position into the glyph position.
1084 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
1085 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
1086 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
1088 // Get the glyph position.
1089 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
1091 // Get the metrics for the group of glyphs.
1092 GlyphMetrics glyphMetrics;
1093 GetGlyphsMetrics( previousGlyphIndex,
1099 // Set the cursor position and height.
1100 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1101 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1103 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1105 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1107 // Update the primary cursor height as well.
1108 cursorInfo.primaryCursorHeight *= 0.5f;
1112 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1114 if( NULL == mEventData )
1116 // Nothing to do if there is no text input.
1120 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1122 const Script script = mLogicalModel->GetScript( index );
1123 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1124 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1126 Length numberOfCharacters = 0u;
1127 if( TextAbstraction::LATIN == script )
1129 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1130 numberOfCharacters = 1u;
1134 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1135 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1137 while( 0u == numberOfCharacters )
1139 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1144 if( index < mEventData->mPrimaryCursorPosition )
1146 cursorIndex -= numberOfCharacters;
1150 cursorIndex += numberOfCharacters;
1156 void Controller::Impl::UpdateCursorPosition()
1158 if( NULL == mEventData )
1160 // Nothing to do if there is no text input.
1164 CursorInfo cursorInfo;
1165 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1168 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1169 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1171 // Sets the cursor position.
1172 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1175 cursorInfo.primaryCursorHeight,
1176 cursorInfo.lineHeight );
1178 // Sets the grab handle position.
1179 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1182 cursorInfo.lineHeight );
1184 if( cursorInfo.isSecondaryCursor )
1186 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1187 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1188 cursorInfo.secondaryPosition.x + offset.x,
1189 cursorInfo.secondaryPosition.y + offset.y,
1190 cursorInfo.secondaryCursorHeight,
1191 cursorInfo.lineHeight );
1195 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1199 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1201 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1202 ( RIGHT_SELECTION_HANDLE != handleType ) )
1207 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1208 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1210 CursorInfo cursorInfo;
1211 GetCursorPosition( index,
1214 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1215 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1217 // Sets the grab handle position.
1218 mEventData->mDecorator->SetPosition( handleType,
1221 cursorInfo.lineHeight );
1224 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1226 // Clamp between -space & 0 (and the text alignment).
1227 if( actualSize.width > mControlSize.width )
1229 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1230 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1231 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1233 mEventData->mDecoratorUpdated = true;
1237 mEventData->mScrollPosition.x = 0.f;
1241 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1243 // Clamp between -space & 0 (and the text alignment).
1244 if( actualSize.height > mControlSize.height )
1246 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1247 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1248 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1250 mEventData->mDecoratorUpdated = true;
1254 mEventData->mScrollPosition.y = 0.f;
1258 void Controller::Impl::ScrollToMakeCursorVisible()
1260 if( NULL == mEventData )
1262 // Nothing to do if there is no text input.
1266 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1269 bool updateDecorator = false;
1270 if( primaryCursorPosition.x < 0.f )
1272 offset.x = -primaryCursorPosition.x;
1273 mEventData->mScrollPosition.x += offset.x;
1274 updateDecorator = true;
1276 else if( primaryCursorPosition.x > mControlSize.width )
1278 offset.x = mControlSize.width - primaryCursorPosition.x;
1279 mEventData->mScrollPosition.x += offset.x;
1280 updateDecorator = true;
1283 if( updateDecorator && mEventData->mDecorator )
1285 mEventData->mDecorator->UpdatePositions( offset );
1288 // TODO : calculate the vertical scroll.
1291 void Controller::Impl::RequestRelayout()
1293 mControlInterface.RequestTextRelayout();
1298 } // namespace Toolkit