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>
28 * @brief Some characters can be shaped in more than one glyph.
29 * This struct is used to retrieve metrics from these group of glyphs.
43 float fontHeight; ///< The font's height of that glyphs.
44 float advance; ///< The sum of all the advances of all the glyphs.
45 float ascender; ///< The font's ascender.
46 float xBearing; ///< The x bearing of the first glyph.
49 const std::string EMPTY_STRING("");
63 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
65 * @param[in] glyphIndex The index to the first glyph.
66 * @param[in] numberOfGlyphs The number of glyphs.
67 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
71 void GetGlyphsMetrics( GlyphIndex glyphIndex,
72 Length numberOfGlyphs,
73 GlyphMetrics& glyphMetrics,
74 VisualModelPtr visualModel,
75 TextAbstraction::FontClient& fontClient )
77 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
79 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
81 Text::FontMetrics fontMetrics;
82 fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics );
84 glyphMetrics.fontHeight = fontMetrics.height;
85 glyphMetrics.advance = firstGlyph.advance;
86 glyphMetrics.ascender = fontMetrics.ascender;
87 glyphMetrics.xBearing = firstGlyph.xBearing;
89 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
91 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
93 glyphMetrics.advance += glyphInfo.advance;
97 EventData::EventData( DecoratorPtr decorator )
98 : mDecorator( decorator ),
103 mPrimaryCursorPosition( 0u ),
104 mLeftSelectionPosition( 0u ),
105 mRightSelectionPosition( 0u ),
106 mDecoratorUpdated( false ),
107 mCursorBlinkEnabled( true ),
108 mGrabHandleEnabled( true ),
109 mGrabHandlePopupEnabled( false ),
110 mSelectionEnabled( false ),
111 mHorizontalScrollingEnabled( true ),
112 mVerticalScrollingEnabled( false ),
113 mUpdateCursorPosition( false ),
114 mUpdateLeftSelectionPosition( false ),
115 mUpdateRightSelectionPosition( false ),
116 mScrollAfterUpdateCursorPosition( false )
119 EventData::~EventData()
122 bool Controller::Impl::ProcessInputEvents()
124 if( NULL == mEventData )
126 // Nothing to do if there is no text input.
130 mEventData->mDecoratorUpdated = false;
132 if( mEventData->mDecorator )
134 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
135 iter != mEventData->mEventQueue.end();
140 case Event::KEYBOARD_FOCUS_GAIN_EVENT:
142 OnKeyboardFocus( true );
145 case Event::KEYBOARD_FOCUS_LOST_EVENT:
147 OnKeyboardFocus( false );
150 case Event::CURSOR_KEY_EVENT:
152 OnCursorKeyEvent( *iter );
155 case Event::TAP_EVENT:
160 case Event::PAN_EVENT:
165 case Event::GRAB_HANDLE_EVENT:
166 case Event::LEFT_SELECTION_HANDLE_EVENT:
167 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
169 OnHandleEvent( *iter );
176 // The cursor must also be repositioned after inserts into the model
177 if( mEventData->mUpdateCursorPosition )
179 // Updates the cursor position and scrolls the text to make it visible.
181 UpdateCursorPosition();
183 if( mEventData->mScrollAfterUpdateCursorPosition )
185 ScrollToMakeCursorVisible();
186 mEventData->mScrollAfterUpdateCursorPosition = false;
189 mEventData->mDecoratorUpdated = true;
190 mEventData->mUpdateCursorPosition = false;
192 else if( mEventData->mUpdateLeftSelectionPosition )
194 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
196 if( mEventData->mScrollAfterUpdateCursorPosition )
198 ScrollToMakeCursorVisible();
199 mEventData->mScrollAfterUpdateCursorPosition = false;
202 mEventData->mDecoratorUpdated = true;
203 mEventData->mUpdateLeftSelectionPosition = false;
205 else if( mEventData->mUpdateRightSelectionPosition )
207 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
209 if( mEventData->mScrollAfterUpdateCursorPosition )
211 ScrollToMakeCursorVisible();
212 mEventData->mScrollAfterUpdateCursorPosition = false;
215 mEventData->mDecoratorUpdated = true;
216 mEventData->mUpdateRightSelectionPosition = false;
219 mEventData->mEventQueue.clear();
221 return mEventData->mDecoratorUpdated;
224 void Controller::Impl::OnKeyboardFocus( bool hasFocus )
226 if( NULL == mEventData )
228 // Nothing to do if there is no text input.
234 ChangeState( EventData::INACTIVE );
238 ChangeState( EventData::EDITING );
242 void Controller::Impl::OnCursorKeyEvent( const Event& event )
244 if( NULL == mEventData )
246 // Nothing to do if there is no text input.
250 int keyCode = event.p1.mInt;
252 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
254 if( mEventData->mPrimaryCursorPosition > 0u )
256 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
259 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
261 if( mLogicalModel->GetNumberOfCharacters() > mEventData->mPrimaryCursorPosition )
263 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
266 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
270 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
275 mEventData->mUpdateCursorPosition = true;
276 mEventData->mScrollAfterUpdateCursorPosition = true;
279 void Controller::Impl::HandleCursorKey( int keyCode )
282 if( NULL == mEventData )
284 // Nothing to do if there is no text input.
289 void Controller::Impl::OnTapEvent( const Event& event )
291 if( NULL == mEventData )
293 // Nothing to do if there is no text input.
297 const unsigned int tapCount = event.p1.mUint;
301 ChangeState( EventData::EDITING );
303 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
304 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
306 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
309 mEventData->mUpdateCursorPosition = true;
310 mEventData->mScrollAfterUpdateCursorPosition = true;
312 else if( mEventData->mSelectionEnabled &&
315 ChangeState( EventData::SELECTING );
317 RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat );
321 void Controller::Impl::OnPanEvent( const Event& event )
323 if( NULL == mEventData )
325 // Nothing to do if there is no text input.
329 int state = event.p1.mInt;
331 if( Gesture::Started == state ||
332 Gesture::Continuing == state )
334 const Vector2& actualSize = mVisualModel->GetActualSize();
335 const Vector2 currentScroll = mEventData->mScrollPosition;
337 if( mEventData->mHorizontalScrollingEnabled )
339 const float displacementX = event.p2.mFloat;
340 mEventData->mScrollPosition.x += displacementX;
342 ClampHorizontalScroll( actualSize );
345 if( mEventData->mVerticalScrollingEnabled )
347 const float displacementY = event.p3.mFloat;
348 mEventData->mScrollPosition.y += displacementY;
350 ClampVerticalScroll( actualSize );
353 if( mEventData->mDecorator )
355 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
360 void Controller::Impl::OnHandleEvent( const Event& event )
362 if( NULL == mEventData )
364 // Nothing to do if there is no text input.
368 const unsigned int state = event.p1.mUint;
370 if( HANDLE_PRESSED == state )
372 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
373 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
374 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
376 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
378 if( Event::GRAB_HANDLE_EVENT == event.type )
380 ChangeState ( EventData::EDITING );
382 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
384 mEventData->mPrimaryCursorPosition = handleNewPosition;
385 mEventData->mUpdateCursorPosition = true;
388 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
390 if( handleNewPosition != mEventData->mLeftSelectionPosition )
392 mEventData->mLeftSelectionPosition = handleNewPosition;
393 mEventData->mUpdateLeftSelectionPosition = true;
396 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
398 if( handleNewPosition != mEventData->mRightSelectionPosition )
400 mEventData->mRightSelectionPosition = handleNewPosition;
401 mEventData->mUpdateRightSelectionPosition = true;
405 else if( ( HANDLE_RELEASED == state ) ||
406 ( HANDLE_STOP_SCROLLING == state ) )
408 if( mEventData->mGrabHandlePopupEnabled )
410 ChangeState( EventData::EDITING_WITH_POPUP );
412 if( Event::GRAB_HANDLE_EVENT == event.type )
414 mEventData->mUpdateCursorPosition = true;
416 if( HANDLE_STOP_SCROLLING == state )
418 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
419 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
420 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
422 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
424 mEventData->mScrollAfterUpdateCursorPosition = true;
427 mEventData->mDecoratorUpdated = true;
429 else if( HANDLE_SCROLLING == state )
431 const float xSpeed = event.p2.mFloat;
432 const Vector2& actualSize = mVisualModel->GetActualSize();
434 mEventData->mScrollPosition.x += xSpeed;
436 ClampHorizontalScroll( actualSize );
438 mEventData->mDecoratorUpdated = true;
442 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
444 if( NULL == mEventData )
446 // Nothing to do if there is no text input.
450 // TODO - Find which word was selected
452 const Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
453 const Vector<Vector2>::SizeType glyphCount = glyphs.Count();
455 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
456 const Vector<Vector2>::SizeType positionCount = positions.Count();
458 // Guard against glyphs which did not fit inside the layout
459 const Vector<Vector2>::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount;
463 float primaryX = positions[0].x + mEventData->mScrollPosition.x;
464 float secondaryX = positions[count-1].x + glyphs[count-1].width + mEventData->mScrollPosition.x;
466 // TODO - multi-line selection
467 const Vector<LineRun>& lines = mVisualModel->mLines;
468 float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f;
470 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryX, mEventData->mScrollPosition.y, height );
471 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height );
473 mEventData->mDecorator->ClearHighlights();
474 mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y );
478 void Controller::Impl::ChangeState( EventData::State newState )
480 if( NULL == mEventData )
482 // Nothing to do if there is no text input.
486 if( mEventData->mState != newState )
488 mEventData->mState = newState;
490 if( EventData::INACTIVE == mEventData->mState )
492 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
493 mEventData->mDecorator->StopCursorBlink();
494 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
495 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
496 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
497 mEventData->mDecorator->SetPopupActive( false );
498 mEventData->mDecoratorUpdated = true;
500 else if ( EventData::SELECTING == mEventData->mState )
502 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
503 mEventData->mDecorator->StopCursorBlink();
504 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
505 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
506 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
507 mEventData->mDecoratorUpdated = true;
509 else if( EventData::EDITING == mEventData->mState )
511 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
512 if( mEventData->mCursorBlinkEnabled )
514 mEventData->mDecorator->StartCursorBlink();
516 if( mEventData->mGrabHandleEnabled )
518 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
520 if( mEventData->mGrabHandlePopupEnabled )
522 mEventData->mDecorator->SetPopupActive( false );
524 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
525 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
526 mEventData->mDecoratorUpdated = true;
528 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
530 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
531 if( mEventData->mCursorBlinkEnabled )
533 mEventData->mDecorator->StartCursorBlink();
535 if( mEventData->mGrabHandleEnabled )
537 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
538 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
540 if( mEventData->mGrabHandlePopupEnabled )
542 mEventData->mDecorator->SetPopupActive( true );
544 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
545 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
546 mEventData->mDecoratorUpdated = true;
551 LineIndex Controller::Impl::GetClosestLine( float y ) const
553 float totalHeight = 0.f;
554 LineIndex lineIndex = 0u;
556 const Vector<LineRun>& lines = mVisualModel->mLines;
557 for( LineIndex endLine = lines.Count();
561 const LineRun& lineRun = lines[lineIndex];
562 totalHeight += lineRun.ascender + -lineRun.descender;
563 if( y < totalHeight )
572 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
575 if( NULL == mEventData )
577 // Nothing to do if there is no text input.
581 CharacterIndex logicalIndex = 0u;
583 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
584 const Length numberOfLines = mVisualModel->mLines.Count();
585 if( 0 == numberOfGlyphs ||
591 // Find which line is closest
592 const LineIndex lineIndex = GetClosestLine( visualY );
593 const LineRun& line = mVisualModel->mLines[lineIndex];
595 // Get the positions of the glyphs.
596 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
597 const Vector2* const positionsBuffer = positions.Begin();
599 // Get the visual to logical conversion tables.
600 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
601 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
603 // Get the character to glyph conversion table.
604 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
606 // Get the glyphs per character table.
607 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
609 // If the vector is void, there is no right to left characters.
610 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
612 const CharacterIndex startCharacter = line.characterRun.characterIndex;
613 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
614 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
616 // Whether there is a hit on a glyph.
617 bool matched = false;
619 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
620 CharacterIndex visualIndex = startCharacter;
621 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
623 // The character in logical order.
624 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
626 // The first glyph for that character in logical order.
627 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
629 // The number of glyphs for that character
630 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
632 // Get the metrics for the group of glyphs.
633 GlyphMetrics glyphMetrics;
634 GetGlyphsMetrics( glyphLogicalOrderIndex,
640 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
642 const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
644 if( visualX < glyphX )
651 // Return the logical position of the cursor in characters.
655 visualIndex = endCharacter;
658 return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
661 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
662 CursorInfo& cursorInfo )
664 // TODO: Check for multiline with \n, etc...
666 // Check if the logical position is the first or the last one of the text.
667 const bool isFirstPosition = 0u == logical;
668 const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
670 if( isFirstPosition && isLastPosition )
672 // There is zero characters. Get the default font.
674 FontId defaultFontId = 0u;
675 if( NULL == mFontDefaults )
677 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
682 defaultFontId = mFontDefaults->GetFontId( mFontClient );
685 Text::FontMetrics fontMetrics;
686 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
688 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
689 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
691 cursorInfo.primaryPosition.x = 0.f;
692 cursorInfo.primaryPosition.y = 0.f;
694 // Nothing else to do.
698 // Get the previous logical index.
699 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
701 // Decrease the logical index if it's the last one.
707 // Get the direction of the character and the previous one.
708 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
710 CharacterDirection isCurrentRightToLeft = false;
711 CharacterDirection isPreviousRightToLeft = false;
712 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
714 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
715 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
718 // Get the line where the character is laid-out.
719 const LineRun* modelLines = mVisualModel->mLines.Begin();
721 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
722 const LineRun& line = *( modelLines + lineIndex );
724 // Get the paragraph's direction.
725 const CharacterDirection isRightToLeftParagraph = line.direction;
727 // Check whether there is an alternative position:
729 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
730 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
732 // Set the line height.
733 cursorInfo.lineHeight = line.ascender + -line.descender;
735 // Convert the cursor position into the glyph position.
736 CharacterIndex characterIndex = logical;
737 if( cursorInfo.isSecondaryCursor &&
738 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
740 characterIndex = previousLogical;
743 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
744 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
745 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
747 // Get the metrics for the group of glyphs.
748 GlyphMetrics glyphMetrics;
749 GetGlyphsMetrics( currentGlyphIndex,
755 float interGlyphAdvance = 0.f;
756 if( !isLastPosition &&
757 ( numberOfCharacters > 1u ) )
759 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
760 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
763 // Get the glyph position and x bearing.
764 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
766 // Set the cursor's height.
767 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
770 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
771 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
775 // The position of the cursor after the last character needs special
776 // care depending on its direction and the direction of the paragraph.
778 if( cursorInfo.isSecondaryCursor )
780 // Need to find the first character after the last character with the paragraph's direction.
781 // i.e l0 l1 l2 r0 r1 should find r0.
783 // TODO: check for more than one line!
784 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
785 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
787 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
788 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
790 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
792 // Get the metrics for the group of glyphs.
793 GlyphMetrics glyphMetrics;
794 GetGlyphsMetrics( glyphIndex,
800 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
802 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
806 if( !isCurrentRightToLeft )
808 cursorInfo.primaryPosition.x += glyphMetrics.advance;
812 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
817 // Set the alternative cursor position.
818 if( cursorInfo.isSecondaryCursor )
820 // Convert the cursor position into the glyph position.
821 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
822 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
823 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
825 // Get the glyph position.
826 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
828 // Get the metrics for the group of glyphs.
829 GlyphMetrics glyphMetrics;
830 GetGlyphsMetrics( previousGlyphIndex,
836 // Set the cursor position and height.
837 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
838 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
840 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
842 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
844 // Update the primary cursor height as well.
845 cursorInfo.primaryCursorHeight *= 0.5f;
849 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
851 if( NULL == mEventData )
853 // Nothing to do if there is no text input.
857 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
859 const Script script = mLogicalModel->GetScript( index );
860 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
861 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
863 Length numberOfCharacters = 0u;
864 if( TextAbstraction::LATIN == script )
866 // Prevents to jump the whole Latin ligatures like fi, ff, ...
867 numberOfCharacters = 1u;
871 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
872 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
874 while( 0u == numberOfCharacters )
876 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
881 if( index < mEventData->mPrimaryCursorPosition )
883 cursorIndex -= numberOfCharacters;
887 cursorIndex += numberOfCharacters;
893 void Controller::Impl::UpdateCursorPosition()
895 if( NULL == mEventData )
897 // Nothing to do if there is no text input.
901 CursorInfo cursorInfo;
902 GetCursorPosition( mEventData->mPrimaryCursorPosition,
905 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
906 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
908 // Sets the cursor position.
909 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
912 cursorInfo.primaryCursorHeight,
913 cursorInfo.lineHeight );
915 // Sets the grab handle position.
916 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
919 cursorInfo.lineHeight );
921 if( cursorInfo.isSecondaryCursor )
923 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
924 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
925 cursorInfo.secondaryPosition.x + offset.x,
926 cursorInfo.secondaryPosition.y + offset.y,
927 cursorInfo.secondaryCursorHeight,
928 cursorInfo.lineHeight );
932 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
936 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
938 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
939 ( RIGHT_SELECTION_HANDLE != handleType ) )
944 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
945 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
947 CursorInfo cursorInfo;
948 GetCursorPosition( index,
951 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
952 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
954 // Sets the grab handle position.
955 mEventData->mDecorator->SetPosition( handleType,
958 cursorInfo.lineHeight );
961 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
963 // Clamp between -space & 0 (and the text alignment).
964 if( actualSize.width > mControlSize.width )
966 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
967 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
968 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
970 mEventData->mDecoratorUpdated = true;
974 mEventData->mScrollPosition.x = 0.f;
978 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
980 // Clamp between -space & 0 (and the text alignment).
981 if( actualSize.height > mControlSize.height )
983 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
984 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
985 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
987 mEventData->mDecoratorUpdated = true;
991 mEventData->mScrollPosition.y = 0.f;
995 void Controller::Impl::ScrollToMakeCursorVisible()
997 if( NULL == mEventData )
999 // Nothing to do if there is no text input.
1003 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1006 bool updateDecorator = false;
1007 if( primaryCursorPosition.x < 0.f )
1009 offset.x = -primaryCursorPosition.x;
1010 mEventData->mScrollPosition.x += offset.x;
1011 updateDecorator = true;
1013 else if( primaryCursorPosition.x > mControlSize.width )
1015 offset.x = mControlSize.width - primaryCursorPosition.x;
1016 mEventData->mScrollPosition.x += offset.x;
1017 updateDecorator = true;
1020 if( updateDecorator && mEventData->mDecorator )
1022 mEventData->mDecorator->UpdatePositions( offset );
1025 // TODO : calculate the vertical scroll.
1028 void Controller::Impl::RequestRelayout()
1030 mControlInterface.RequestTextRelayout();
1035 } // namespace Toolkit