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( false ),
132 mSelectionEnabled( false ),
133 mHorizontalScrollingEnabled( true ),
134 mVerticalScrollingEnabled( false ),
135 mUpdateCursorPosition( false ),
136 mUpdateLeftSelectionPosition( false ),
137 mUpdateRightSelectionPosition( false ),
138 mScrollAfterUpdateCursorPosition( 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->mScrollAfterUpdateCursorPosition )
198 ScrollToMakeCursorVisible();
199 mEventData->mScrollAfterUpdateCursorPosition = false;
202 mEventData->mDecoratorUpdated = true;
203 mEventData->mUpdateCursorPosition = false;
205 else if( mEventData->mScrollAfterDelete )
207 ScrollTextToMatchCursor();
208 mEventData->mDecoratorUpdated = true;
209 mEventData->mScrollAfterDelete = false;
211 else if( mEventData->mUpdateLeftSelectionPosition )
213 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
215 if( mEventData->mScrollAfterUpdateCursorPosition )
217 ScrollToMakeCursorVisible();
218 mEventData->mScrollAfterUpdateCursorPosition = false;
221 mEventData->mDecoratorUpdated = true;
222 mEventData->mUpdateLeftSelectionPosition = false;
224 else if( mEventData->mUpdateRightSelectionPosition )
226 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
228 if( mEventData->mScrollAfterUpdateCursorPosition )
230 ScrollToMakeCursorVisible();
231 mEventData->mScrollAfterUpdateCursorPosition = false;
234 mEventData->mDecoratorUpdated = true;
235 mEventData->mUpdateRightSelectionPosition = false;
238 mEventData->mEventQueue.clear();
240 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
242 bool decoratorUpdated = mEventData->mDecoratorUpdated;
243 mEventData->mDecoratorUpdated = false;
244 return decoratorUpdated;
247 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
249 // Calculate the operations to be done.
250 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
252 Vector<Character>& utf32Characters = mLogicalModel->mText;
254 const Length numberOfCharacters = utf32Characters.Count();
256 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
257 if( GET_LINE_BREAKS & operations )
259 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
260 // calculate the bidirectional info for each 'paragraph'.
261 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
262 // is not shaped together).
263 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
265 SetLineBreakInfo( utf32Characters,
269 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
270 if( GET_WORD_BREAKS & operations )
272 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
273 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
275 SetWordBreakInfo( utf32Characters,
279 const bool getScripts = GET_SCRIPTS & operations;
280 const bool validateFonts = VALIDATE_FONTS & operations;
282 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
283 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
285 if( getScripts || validateFonts )
287 // Validates the fonts assigned by the application or assigns default ones.
288 // It makes sure all the characters are going to be rendered by the correct font.
289 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
293 // Retrieves the scripts used in the text.
294 multilanguageSupport.SetScripts( utf32Characters,
301 if( 0u == validFonts.Count() )
303 // Copy the requested font defaults received via the property system.
304 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
305 GetDefaultFonts( validFonts, numberOfCharacters );
308 // Validates the fonts. If there is a character with no assigned font it sets a default one.
309 // After this call, fonts are validated.
310 multilanguageSupport.ValidateFonts( utf32Characters,
316 Vector<Character> mirroredUtf32Characters;
317 bool textMirrored = false;
318 if( BIDI_INFO & operations )
320 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
321 // bidirectional info.
323 Length numberOfParagraphs = 0u;
325 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
326 for( Length index = 0u; index < numberOfCharacters; ++index )
328 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
330 ++numberOfParagraphs;
334 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
335 bidirectionalInfo.Reserve( numberOfParagraphs );
337 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
338 SetBidirectionalInfo( utf32Characters,
343 if( 0u != bidirectionalInfo.Count() )
345 // This paragraph has right to left text. Some characters may need to be mirrored.
346 // TODO: consider if the mirrored string can be stored as well.
348 textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
350 // Only set the character directions if there is right to left characters.
351 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
352 directions.Resize( numberOfCharacters );
354 GetCharactersDirection( bidirectionalInfo,
359 // There is no right to left characters. Clear the directions vector.
360 mLogicalModel->mCharacterDirections.Clear();
365 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
366 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
367 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
368 if( SHAPE_TEXT & operations )
370 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
372 ShapeText( textToShape,
377 glyphsToCharactersMap,
378 charactersPerGlyph );
380 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
381 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
382 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
385 const Length numberOfGlyphs = glyphs.Count();
387 if( GET_GLYPH_METRICS & operations )
389 mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
393 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
398 fontRun.characterRun.characterIndex = 0;
399 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
400 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
401 fontRun.isDefault = true;
403 fonts.PushBack( fontRun );
407 void Controller::Impl::OnCursorKeyEvent( const Event& event )
409 if( NULL == mEventData )
411 // Nothing to do if there is no text input.
415 int keyCode = event.p1.mInt;
417 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
419 if( mEventData->mPrimaryCursorPosition > 0u )
421 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
424 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
426 if( mLogicalModel->GetNumberOfCharacters() > mEventData->mPrimaryCursorPosition )
428 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
431 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
435 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
440 mEventData->mUpdateCursorPosition = true;
441 mEventData->mScrollAfterUpdateCursorPosition = true;
444 void Controller::Impl::OnTapEvent( const Event& event )
446 if( NULL != mEventData )
448 const unsigned int tapCount = event.p1.mUint;
452 if( ! IsShowingPlaceholderText() )
454 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
455 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
457 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
462 mEventData->mPrimaryCursorPosition = 0u;
465 mEventData->mUpdateCursorPosition = true;
466 mEventData->mScrollAfterUpdateCursorPosition = true;
468 else if( mEventData->mSelectionEnabled &&
471 RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat );
476 void Controller::Impl::OnPanEvent( const Event& event )
478 if( NULL == mEventData )
480 // Nothing to do if there is no text input.
484 int state = event.p1.mInt;
486 if( Gesture::Started == state ||
487 Gesture::Continuing == state )
489 const Vector2& actualSize = mVisualModel->GetActualSize();
490 const Vector2 currentScroll = mEventData->mScrollPosition;
492 if( mEventData->mHorizontalScrollingEnabled )
494 const float displacementX = event.p2.mFloat;
495 mEventData->mScrollPosition.x += displacementX;
497 ClampHorizontalScroll( actualSize );
500 if( mEventData->mVerticalScrollingEnabled )
502 const float displacementY = event.p3.mFloat;
503 mEventData->mScrollPosition.y += displacementY;
505 ClampVerticalScroll( actualSize );
508 if( mEventData->mDecorator )
510 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
515 void Controller::Impl::OnHandleEvent( const Event& event )
517 if( NULL == mEventData )
519 // Nothing to do if there is no text input.
523 const unsigned int state = event.p1.mUint;
525 if( HANDLE_PRESSED == state )
527 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
528 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
529 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
531 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
533 if( Event::GRAB_HANDLE_EVENT == event.type )
535 ChangeState ( EventData::EDITING );
537 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
539 mEventData->mPrimaryCursorPosition = handleNewPosition;
540 mEventData->mUpdateCursorPosition = true;
543 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
545 if( handleNewPosition != mEventData->mLeftSelectionPosition )
547 mEventData->mLeftSelectionPosition = handleNewPosition;
548 mEventData->mUpdateLeftSelectionPosition = true;
551 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
553 if( handleNewPosition != mEventData->mRightSelectionPosition )
555 mEventData->mRightSelectionPosition = handleNewPosition;
556 mEventData->mUpdateRightSelectionPosition = true;
560 else if( ( HANDLE_RELEASED == state ) ||
561 ( HANDLE_STOP_SCROLLING == state ) )
563 if( mEventData->mGrabHandlePopupEnabled )
565 ChangeState( EventData::EDITING_WITH_POPUP );
567 if( Event::GRAB_HANDLE_EVENT == event.type )
569 mEventData->mUpdateCursorPosition = true;
571 if( HANDLE_STOP_SCROLLING == state )
573 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
574 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
575 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
577 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
579 mEventData->mScrollAfterUpdateCursorPosition = true;
582 mEventData->mDecoratorUpdated = true;
584 else if( HANDLE_SCROLLING == state )
586 const float xSpeed = event.p2.mFloat;
587 const Vector2& actualSize = mVisualModel->GetActualSize();
589 mEventData->mScrollPosition.x += xSpeed;
591 ClampHorizontalScroll( actualSize );
593 mEventData->mDecoratorUpdated = true;
597 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
599 if( NULL == mEventData )
601 // Nothing to do if there is no text input.
605 // TODO - Find which word was selected
607 const Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
608 const Vector<Vector2>::SizeType glyphCount = glyphs.Count();
610 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
611 const Vector<Vector2>::SizeType positionCount = positions.Count();
613 // Guard against glyphs which did not fit inside the layout
614 const Vector<Vector2>::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount;
618 float primaryX = positions[0].x + mEventData->mScrollPosition.x;
619 float secondaryX = positions[count-1].x + glyphs[count-1].width + mEventData->mScrollPosition.x;
621 // TODO - multi-line selection
622 const Vector<LineRun>& lines = mVisualModel->mLines;
623 float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f;
625 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryX, mEventData->mScrollPosition.y, height );
626 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height );
628 mEventData->mDecorator->ClearHighlights();
629 mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y );
633 void Controller::Impl::ChangeState( EventData::State newState )
635 if( NULL == mEventData )
637 // Nothing to do if there is no text input.
641 if( mEventData->mState != newState )
643 mEventData->mState = newState;
645 if( EventData::INACTIVE == mEventData->mState )
647 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
648 mEventData->mDecorator->StopCursorBlink();
649 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
650 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
651 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
652 mEventData->mDecorator->SetPopupActive( false );
653 mEventData->mDecoratorUpdated = true;
655 else if ( EventData::SELECTING == mEventData->mState )
657 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
658 mEventData->mDecorator->StopCursorBlink();
659 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
660 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
661 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
662 mEventData->mDecoratorUpdated = true;
664 else if( EventData::EDITING == mEventData->mState )
666 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
667 if( mEventData->mCursorBlinkEnabled )
669 mEventData->mDecorator->StartCursorBlink();
671 // Grab handle is not shown until a tap is received whilst EDITING
672 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
673 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
674 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
675 mEventData->mDecoratorUpdated = true;
677 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
679 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
680 if( mEventData->mCursorBlinkEnabled )
682 mEventData->mDecorator->StartCursorBlink();
684 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
685 if( mEventData->mSelectionEnabled )
687 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
688 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
690 if( mEventData->mGrabHandlePopupEnabled )
692 mEventData->mDecorator->SetPopupActive( true );
694 mEventData->mDecoratorUpdated = true;
699 LineIndex Controller::Impl::GetClosestLine( float y ) const
701 float totalHeight = 0.f;
702 LineIndex lineIndex = 0u;
704 const Vector<LineRun>& lines = mVisualModel->mLines;
705 for( LineIndex endLine = lines.Count();
709 const LineRun& lineRun = lines[lineIndex];
710 totalHeight += lineRun.ascender + -lineRun.descender;
711 if( y < totalHeight )
720 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
723 if( NULL == mEventData )
725 // Nothing to do if there is no text input.
729 CharacterIndex logicalIndex = 0u;
731 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
732 const Length numberOfLines = mVisualModel->mLines.Count();
733 if( 0 == numberOfGlyphs ||
739 // Find which line is closest
740 const LineIndex lineIndex = GetClosestLine( visualY );
741 const LineRun& line = mVisualModel->mLines[lineIndex];
743 // Get the positions of the glyphs.
744 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
745 const Vector2* const positionsBuffer = positions.Begin();
747 // Get the visual to logical conversion tables.
748 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
749 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
751 // Get the character to glyph conversion table.
752 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
754 // Get the glyphs per character table.
755 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
757 // If the vector is void, there is no right to left characters.
758 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
760 const CharacterIndex startCharacter = line.characterRun.characterIndex;
761 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
762 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
764 // Whether there is a hit on a glyph.
765 bool matched = false;
767 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
768 CharacterIndex visualIndex = startCharacter;
769 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
771 // The character in logical order.
772 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
774 // The first glyph for that character in logical order.
775 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
777 // The number of glyphs for that character
778 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
780 // Get the metrics for the group of glyphs.
781 GlyphMetrics glyphMetrics;
782 GetGlyphsMetrics( glyphLogicalOrderIndex,
788 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
790 const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
792 if( visualX < glyphX )
799 // Return the logical position of the cursor in characters.
803 visualIndex = endCharacter;
806 return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
809 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
810 CursorInfo& cursorInfo )
812 // TODO: Check for multiline with \n, etc...
814 // Check if the logical position is the first or the last one of the text.
815 const bool isFirstPosition = 0u == logical;
816 const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
818 if( isFirstPosition && isLastPosition )
820 // There is zero characters. Get the default font.
822 FontId defaultFontId = 0u;
823 if( NULL == mFontDefaults )
825 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
830 defaultFontId = mFontDefaults->GetFontId( mFontClient );
833 Text::FontMetrics fontMetrics;
834 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
836 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
837 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
839 cursorInfo.primaryPosition.x = 0.f;
840 cursorInfo.primaryPosition.y = 0.f;
842 // Nothing else to do.
846 // Get the previous logical index.
847 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
849 // Decrease the logical index if it's the last one.
855 // Get the direction of the character and the previous one.
856 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
858 CharacterDirection isCurrentRightToLeft = false;
859 CharacterDirection isPreviousRightToLeft = false;
860 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
862 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
863 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
866 // Get the line where the character is laid-out.
867 const LineRun* modelLines = mVisualModel->mLines.Begin();
869 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
870 const LineRun& line = *( modelLines + lineIndex );
872 // Get the paragraph's direction.
873 const CharacterDirection isRightToLeftParagraph = line.direction;
875 // Check whether there is an alternative position:
877 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
878 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
880 // Set the line height.
881 cursorInfo.lineHeight = line.ascender + -line.descender;
883 // Convert the cursor position into the glyph position.
884 CharacterIndex characterIndex = logical;
885 if( cursorInfo.isSecondaryCursor &&
886 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
888 characterIndex = previousLogical;
891 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
892 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
893 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
895 // Get the metrics for the group of glyphs.
896 GlyphMetrics glyphMetrics;
897 GetGlyphsMetrics( currentGlyphIndex,
903 float interGlyphAdvance = 0.f;
904 if( !isLastPosition &&
905 ( numberOfCharacters > 1u ) )
907 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
908 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
911 // Get the glyph position and x bearing.
912 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
914 // Set the cursor's height.
915 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
918 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
919 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
923 // The position of the cursor after the last character needs special
924 // care depending on its direction and the direction of the paragraph.
926 if( cursorInfo.isSecondaryCursor )
928 // Need to find the first character after the last character with the paragraph's direction.
929 // i.e l0 l1 l2 r0 r1 should find r0.
931 // TODO: check for more than one line!
932 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
933 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
935 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
936 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
938 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
940 // Get the metrics for the group of glyphs.
941 GlyphMetrics glyphMetrics;
942 GetGlyphsMetrics( glyphIndex,
948 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
950 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
954 if( !isCurrentRightToLeft )
956 cursorInfo.primaryPosition.x += glyphMetrics.advance;
960 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
965 // Set the alternative cursor position.
966 if( cursorInfo.isSecondaryCursor )
968 // Convert the cursor position into the glyph position.
969 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
970 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
971 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
973 // Get the glyph position.
974 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
976 // Get the metrics for the group of glyphs.
977 GlyphMetrics glyphMetrics;
978 GetGlyphsMetrics( previousGlyphIndex,
984 // Set the cursor position and height.
985 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
986 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
988 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
990 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
992 // Update the primary cursor height as well.
993 cursorInfo.primaryCursorHeight *= 0.5f;
997 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
999 if( NULL == mEventData )
1001 // Nothing to do if there is no text input.
1005 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1007 const Script script = mLogicalModel->GetScript( index );
1008 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1009 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1011 Length numberOfCharacters = 0u;
1012 if( TextAbstraction::LATIN == script )
1014 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1015 numberOfCharacters = 1u;
1019 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1020 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1022 while( 0u == numberOfCharacters )
1024 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1029 if( index < mEventData->mPrimaryCursorPosition )
1031 cursorIndex -= numberOfCharacters;
1035 cursorIndex += numberOfCharacters;
1041 void Controller::Impl::UpdateCursorPosition()
1043 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1044 if( NULL == mEventData )
1046 // Nothing to do if there is no text input.
1047 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1051 if( IsShowingPlaceholderText() )
1053 // Do not want to use the place-holder text to set the cursor position.
1055 // Use the line's height of the font's family set to set the cursor's size.
1056 // If there is no font's family set, use the default font.
1057 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1059 float lineHeight = 0.f;
1061 FontId defaultFontId = 0u;
1062 if( NULL == mFontDefaults )
1064 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
1069 defaultFontId = mFontDefaults->GetFontId( mFontClient );
1072 Text::FontMetrics fontMetrics;
1073 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
1075 lineHeight = fontMetrics.ascender - fontMetrics.descender;
1078 Vector2 cursorPosition;
1080 switch( mLayoutEngine.GetHorizontalAlignment() )
1082 case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
1084 cursorPosition.x = 1.f;
1087 case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
1089 cursorPosition.x = floor( 0.5f * mControlSize.width );
1092 case LayoutEngine::HORIZONTAL_ALIGN_END:
1094 cursorPosition.x = mControlSize.width;
1099 switch( mLayoutEngine.GetVerticalAlignment() )
1101 case LayoutEngine::VERTICAL_ALIGN_TOP:
1103 cursorPosition.y = 0.f;
1106 case LayoutEngine::VERTICAL_ALIGN_CENTER:
1108 cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
1111 case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
1113 cursorPosition.y = mControlSize.height - lineHeight;
1118 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1126 CursorInfo cursorInfo;
1127 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1130 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1131 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1133 // Sets the cursor position.
1134 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1137 cursorInfo.primaryCursorHeight,
1138 cursorInfo.lineHeight );
1139 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
1141 // Sets the grab handle position.
1142 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1145 cursorInfo.lineHeight );
1147 if( cursorInfo.isSecondaryCursor )
1149 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1150 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1151 cursorInfo.secondaryPosition.x + offset.x,
1152 cursorInfo.secondaryPosition.y + offset.y,
1153 cursorInfo.secondaryCursorHeight,
1154 cursorInfo.lineHeight );
1155 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1159 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1162 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1165 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1167 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1168 ( RIGHT_SELECTION_HANDLE != handleType ) )
1173 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1174 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1176 CursorInfo cursorInfo;
1177 GetCursorPosition( index,
1180 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1181 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1183 // Sets the grab handle position.
1184 mEventData->mDecorator->SetPosition( handleType,
1187 cursorInfo.lineHeight );
1190 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1192 // Clamp between -space & 0 (and the text alignment).
1193 if( actualSize.width > mControlSize.width )
1195 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1196 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1197 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1199 mEventData->mDecoratorUpdated = true;
1203 mEventData->mScrollPosition.x = 0.f;
1207 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1209 // Clamp between -space & 0 (and the text alignment).
1210 if( actualSize.height > mControlSize.height )
1212 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1213 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1214 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1216 mEventData->mDecoratorUpdated = true;
1220 mEventData->mScrollPosition.y = 0.f;
1224 void Controller::Impl::ScrollToMakeCursorVisible()
1226 if( NULL == mEventData )
1228 // Nothing to do if there is no text input.
1232 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1235 bool updateDecorator = false;
1236 if( primaryCursorPosition.x < 0.f )
1238 offset.x = -primaryCursorPosition.x;
1239 mEventData->mScrollPosition.x += offset.x;
1240 updateDecorator = true;
1242 else if( primaryCursorPosition.x > mControlSize.width )
1244 offset.x = mControlSize.width - primaryCursorPosition.x;
1245 mEventData->mScrollPosition.x += offset.x;
1246 updateDecorator = true;
1249 if( updateDecorator && mEventData->mDecorator )
1251 mEventData->mDecorator->UpdatePositions( offset );
1254 // TODO : calculate the vertical scroll.
1257 void Controller::Impl::ScrollTextToMatchCursor()
1259 // Get the current cursor position in decorator coords.
1260 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1262 // Calculate the new cursor position.
1263 CursorInfo cursorInfo;
1264 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1267 // Calculate the offset to match the cursor position before the character was deleted.
1268 mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
1270 ClampHorizontalScroll( mVisualModel->GetActualSize() );
1271 bool updateCursorPosition = true;
1273 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1274 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1276 if( updateCursorPosition )
1278 // Sets the cursor position.
1279 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1282 cursorInfo.primaryCursorHeight,
1283 cursorInfo.lineHeight );
1285 // Sets the grab handle position.
1286 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1289 cursorInfo.lineHeight );
1291 if( cursorInfo.isSecondaryCursor )
1293 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1294 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1295 cursorInfo.secondaryPosition.x + offset.x,
1296 cursorInfo.secondaryPosition.y + offset.y,
1297 cursorInfo.secondaryCursorHeight,
1298 cursorInfo.lineHeight );
1299 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1303 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1308 void Controller::Impl::RequestRelayout()
1310 mControlInterface.RequestTextRelayout();
1315 } // namespace Toolkit