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 mSecondaryCursorPosition( 0u ),
105 mDecoratorUpdated( false ),
106 mCursorBlinkEnabled( true ),
107 mGrabHandleEnabled( true ),
108 mGrabHandlePopupEnabled( true ),
109 mSelectionEnabled( true ),
110 mHorizontalScrollingEnabled( true ),
111 mVerticalScrollingEnabled( false ),
112 mUpdateCursorPosition( false ),
113 mScrollAfterUpdateCursorPosition( false )
116 EventData::~EventData()
119 bool Controller::Impl::ProcessInputEvents()
121 if( NULL == mEventData )
123 // Nothing to do if there is no text input.
127 mEventData->mDecoratorUpdated = false;
129 if( mEventData->mDecorator )
131 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
132 iter != mEventData->mEventQueue.end();
137 case Event::KEYBOARD_FOCUS_GAIN_EVENT:
139 OnKeyboardFocus( true );
142 case Event::KEYBOARD_FOCUS_LOST_EVENT:
144 OnKeyboardFocus( false );
147 case Event::CURSOR_KEY_EVENT:
149 OnCursorKeyEvent( *iter );
152 case Event::TAP_EVENT:
157 case Event::PAN_EVENT:
162 case Event::GRAB_HANDLE_EVENT:
164 OnGrabHandleEvent( *iter );
171 // The cursor must also be repositioned after inserts into the model
172 if( mEventData->mUpdateCursorPosition )
174 // Updates the cursor position and scrolls the text to make it visible.
176 UpdateCursorPosition();
178 if( mEventData->mScrollAfterUpdateCursorPosition )
180 ScrollToMakeCursorVisible();
181 mEventData->mScrollAfterUpdateCursorPosition = false;
184 mEventData->mUpdateCursorPosition = false;
187 mEventData->mEventQueue.clear();
189 return mEventData->mDecoratorUpdated;
192 void Controller::Impl::OnKeyboardFocus( bool hasFocus )
194 if( NULL == mEventData )
196 // Nothing to do if there is no text input.
202 ChangeState( EventData::INACTIVE );
206 ChangeState( EventData::EDITING );
210 void Controller::Impl::OnCursorKeyEvent( const Event& event )
212 if( NULL == mEventData )
214 // Nothing to do if there is no text input.
218 int keyCode = event.p1.mInt;
220 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
222 if( mEventData->mPrimaryCursorPosition > 0u )
224 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
227 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
229 if( mLogicalModel->GetNumberOfCharacters() > mEventData->mPrimaryCursorPosition )
231 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
234 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
238 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
243 mEventData->mUpdateCursorPosition = true;
244 mEventData->mScrollAfterUpdateCursorPosition = true;
247 void Controller::Impl::HandleCursorKey( int keyCode )
250 if( NULL == mEventData )
252 // Nothing to do if there is no text input.
257 void Controller::Impl::OnTapEvent( const Event& event )
259 if( NULL == mEventData )
261 // Nothing to do if there is no text input.
265 const unsigned int tapCount = event.p1.mUint;
269 ChangeState( EventData::EDITING );
271 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
272 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
274 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
277 mEventData->mUpdateCursorPosition = true;
278 mEventData->mScrollAfterUpdateCursorPosition = true;
280 else if( mEventData->mSelectionEnabled &&
283 ChangeState( EventData::SELECTING );
285 RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat );
289 void Controller::Impl::OnPanEvent( const Event& event )
291 if( NULL == mEventData )
293 // Nothing to do if there is no text input.
297 int state = event.p1.mInt;
299 if( Gesture::Started == state ||
300 Gesture::Continuing == state )
302 const Vector2& actualSize = mVisualModel->GetActualSize();
303 const Vector2 currentScroll = mEventData->mScrollPosition;
305 if( mEventData->mHorizontalScrollingEnabled )
307 const float displacementX = event.p2.mFloat;
308 mEventData->mScrollPosition.x += displacementX;
310 ClampHorizontalScroll( actualSize );
313 if( mEventData->mVerticalScrollingEnabled )
315 const float displacementY = event.p3.mFloat;
316 mEventData->mScrollPosition.y += displacementY;
318 ClampVerticalScroll( actualSize );
321 if( mEventData->mDecorator )
323 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
328 void Controller::Impl::OnGrabHandleEvent( const Event& event )
330 if( NULL == mEventData )
332 // Nothing to do if there is no text input.
336 unsigned int state = event.p1.mUint;
338 if( GRAB_HANDLE_PRESSED == state )
340 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
341 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
342 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
344 //mDecorator->HidePopup();
345 ChangeState ( EventData::EDITING );
347 const CharacterIndex newCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
349 if( newCursorPosition != mEventData->mPrimaryCursorPosition )
351 mEventData->mPrimaryCursorPosition = newCursorPosition;
352 mEventData->mUpdateCursorPosition = true;
355 else if( mEventData->mGrabHandlePopupEnabled &&
356 ( ( GRAB_HANDLE_RELEASED == state ) ||
357 ( GRAB_HANDLE_STOP_SCROLLING == state ) ) )
359 //mDecorator->ShowPopup();
360 ChangeState ( EventData::EDITING_WITH_POPUP );
361 mEventData->mUpdateCursorPosition = true;
362 mEventData->mDecoratorUpdated = true;
364 if( GRAB_HANDLE_STOP_SCROLLING == state )
366 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
367 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
368 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
370 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
372 mEventData->mScrollAfterUpdateCursorPosition = true;
375 else if( GRAB_HANDLE_SCROLLING == state )
377 const float xSpeed = event.p2.mFloat;
378 const Vector2& actualSize = mVisualModel->GetActualSize();
380 mEventData->mScrollPosition.x += xSpeed;
382 ClampHorizontalScroll( actualSize );
386 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
388 if( NULL == mEventData )
390 // Nothing to do if there is no text input.
394 // TODO - Find which word was selected
396 const Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
397 const Vector<Vector2>::SizeType glyphCount = glyphs.Count();
399 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
400 const Vector<Vector2>::SizeType positionCount = positions.Count();
402 // Guard against glyphs which did not fit inside the layout
403 const Vector<Vector2>::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount;
407 float primaryX = positions[0].x + mEventData->mScrollPosition.x;
408 float secondaryX = positions[count-1].x + glyphs[count-1].width + mEventData->mScrollPosition.x;
410 // TODO - multi-line selection
411 const Vector<LineRun>& lines = mVisualModel->mLines;
412 float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f;
414 mEventData->mDecorator->SetPosition( PRIMARY_SELECTION_HANDLE, primaryX, mEventData->mScrollPosition.y, height );
415 mEventData->mDecorator->SetPosition( SECONDARY_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height );
417 mEventData->mDecorator->ClearHighlights();
418 mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y );
422 void Controller::Impl::ChangeState( EventData::State newState )
424 if( NULL == mEventData )
426 // Nothing to do if there is no text input.
430 if( mEventData->mState != newState )
432 mEventData->mState = newState;
434 if( EventData::INACTIVE == mEventData->mState )
436 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
437 mEventData->mDecorator->StopCursorBlink();
438 mEventData->mDecorator->SetGrabHandleActive( false );
439 mEventData->mDecorator->SetSelectionActive( false );
440 mEventData->mDecorator->SetPopupActive( false );
441 mEventData->mDecoratorUpdated = true;
443 else if ( EventData::SELECTING == mEventData->mState )
445 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
446 mEventData->mDecorator->StopCursorBlink();
447 mEventData->mDecorator->SetGrabHandleActive( false );
448 mEventData->mDecorator->SetSelectionActive( true );
449 mEventData->mDecoratorUpdated = true;
451 else if( EventData::EDITING == mEventData->mState )
453 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
454 if( mEventData->mCursorBlinkEnabled )
456 mEventData->mDecorator->StartCursorBlink();
458 if( mEventData->mGrabHandleEnabled )
460 mEventData->mDecorator->SetGrabHandleActive( true );
462 if( mEventData->mGrabHandlePopupEnabled )
464 mEventData->mDecorator->SetPopupActive( false );
466 mEventData->mDecorator->SetSelectionActive( false );
467 mEventData->mDecoratorUpdated = true;
469 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
471 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
472 if( mEventData->mCursorBlinkEnabled )
474 mEventData->mDecorator->StartCursorBlink();
476 if( mEventData->mGrabHandleEnabled )
478 mEventData->mDecorator->SetGrabHandleActive( true );
480 if( mEventData->mGrabHandlePopupEnabled )
482 mEventData->mDecorator->SetPopupActive( true );
484 mEventData->mDecorator->SetSelectionActive( false );
485 mEventData->mDecoratorUpdated = true;
490 LineIndex Controller::Impl::GetClosestLine( float y ) const
492 float totalHeight = 0.f;
493 LineIndex lineIndex = 0u;
495 const Vector<LineRun>& lines = mVisualModel->mLines;
496 for( LineIndex endLine = lines.Count();
500 const LineRun& lineRun = lines[lineIndex];
501 totalHeight += lineRun.ascender + -lineRun.descender;
502 if( y < totalHeight )
511 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
514 if( NULL == mEventData )
516 // Nothing to do if there is no text input.
520 CharacterIndex logicalIndex = 0u;
522 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
523 const Length numberOfLines = mVisualModel->mLines.Count();
524 if( 0 == numberOfGlyphs ||
530 // Find which line is closest
531 const LineIndex lineIndex = GetClosestLine( visualY );
532 const LineRun& line = mVisualModel->mLines[lineIndex];
534 // Get the positions of the glyphs.
535 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
536 const Vector2* const positionsBuffer = positions.Begin();
538 // Get the visual to logical conversion tables.
539 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
540 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
542 // Get the character to glyph conversion table.
543 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
545 // Get the glyphs per character table.
546 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
548 // If the vector is void, there is no right to left characters.
549 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
551 const CharacterIndex startCharacter = line.characterRun.characterIndex;
552 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
553 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
555 // Whether there is a hit on a glyph.
556 bool matched = false;
558 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
559 CharacterIndex visualIndex = startCharacter;
560 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
562 // The character in logical order.
563 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
565 // The first glyph for that character in logical order.
566 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
568 // The number of glyphs for that character
569 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
571 // Get the metrics for the group of glyphs.
572 GlyphMetrics glyphMetrics;
573 GetGlyphsMetrics( glyphLogicalOrderIndex,
579 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
581 const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
583 if( visualX < glyphX )
590 // Return the logical position of the cursor in characters.
594 visualIndex = endCharacter;
597 return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
600 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
601 CursorInfo& cursorInfo )
603 // TODO: Check for multiline with \n, etc...
605 // Check if the logical position is the first or the last one of the text.
606 const bool isFirstPosition = 0u == logical;
607 const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
609 if( isFirstPosition && isLastPosition )
611 // There is zero characters. Get the default font.
613 FontId defaultFontId = 0u;
614 if( NULL == mFontDefaults )
616 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
621 defaultFontId = mFontDefaults->GetFontId( mFontClient );
624 Text::FontMetrics fontMetrics;
625 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
627 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
628 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
630 cursorInfo.primaryPosition.x = 0.f;
631 cursorInfo.primaryPosition.y = 0.f;
633 // Nothing else to do.
637 // Get the previous logical index.
638 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
640 // Decrease the logical index if it's the last one.
646 // Get the direction of the character and the previous one.
647 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
649 CharacterDirection isCurrentRightToLeft = false;
650 CharacterDirection isPreviousRightToLeft = false;
651 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
653 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
654 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
657 // Get the line where the character is laid-out.
658 const LineRun* modelLines = mVisualModel->mLines.Begin();
660 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
661 const LineRun& line = *( modelLines + lineIndex );
663 // Get the paragraph's direction.
664 const CharacterDirection isRightToLeftParagraph = line.direction;
666 // Check whether there is an alternative position:
668 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
669 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
671 // Set the line height.
672 cursorInfo.lineHeight = line.ascender + -line.descender;
674 // Convert the cursor position into the glyph position.
675 CharacterIndex characterIndex = logical;
676 if( cursorInfo.isSecondaryCursor &&
677 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
679 characterIndex = previousLogical;
682 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
683 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
684 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
686 // Get the metrics for the group of glyphs.
687 GlyphMetrics glyphMetrics;
688 GetGlyphsMetrics( currentGlyphIndex,
694 float interGlyphAdvance = 0.f;
695 if( !isLastPosition &&
696 ( numberOfCharacters > 1u ) )
698 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
699 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
702 // Get the glyph position and x bearing.
703 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
705 // Set the cursor's height.
706 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
709 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
710 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
714 // The position of the cursor after the last character needs special
715 // care depending on its direction and the direction of the paragraph.
717 if( cursorInfo.isSecondaryCursor )
719 // Need to find the first character after the last character with the paragraph's direction.
720 // i.e l0 l1 l2 r0 r1 should find r0.
722 // TODO: check for more than one line!
723 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
724 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
726 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
727 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
729 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
731 // Get the metrics for the group of glyphs.
732 GlyphMetrics glyphMetrics;
733 GetGlyphsMetrics( glyphIndex,
739 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
741 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
745 if( !isCurrentRightToLeft )
747 cursorInfo.primaryPosition.x += glyphMetrics.advance;
751 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
756 // Set the alternative cursor position.
757 if( cursorInfo.isSecondaryCursor )
759 // Convert the cursor position into the glyph position.
760 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
761 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
762 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
764 // Get the glyph position.
765 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
767 // Get the metrics for the group of glyphs.
768 GlyphMetrics glyphMetrics;
769 GetGlyphsMetrics( previousGlyphIndex,
775 // Set the cursor position and height.
776 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
777 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
779 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
781 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
783 // Update the primary cursor height as well.
784 cursorInfo.primaryCursorHeight *= 0.5f;
788 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
790 if( NULL == mEventData )
792 // Nothing to do if there is no text input.
796 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
798 const Script script = mLogicalModel->GetScript( index );
799 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
800 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
802 Length numberOfCharacters = 0u;
803 if( TextAbstraction::LATIN == script )
805 // Prevents to jump the whole Latin ligatures like fi, ff, ...
806 numberOfCharacters = 1u;
810 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
811 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
813 while( 0u == numberOfCharacters )
815 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
820 if( index < mEventData->mPrimaryCursorPosition )
822 cursorIndex -= numberOfCharacters;
826 cursorIndex += numberOfCharacters;
832 void Controller::Impl::UpdateCursorPosition()
834 if( NULL == mEventData )
836 // Nothing to do if there is no text input.
840 CursorInfo cursorInfo;
841 GetCursorPosition( mEventData->mPrimaryCursorPosition,
844 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
845 cursorInfo.primaryPosition.x + mEventData->mScrollPosition.x + mAlignmentOffset.x,
846 cursorInfo.primaryPosition.y + mEventData->mScrollPosition.y + mAlignmentOffset.y,
847 cursorInfo.primaryCursorHeight,
848 cursorInfo.lineHeight );
850 if( cursorInfo.isSecondaryCursor )
852 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
853 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
854 cursorInfo.secondaryPosition.x + mEventData->mScrollPosition.x + mAlignmentOffset.x,
855 cursorInfo.secondaryPosition.y + mEventData->mScrollPosition.y + mAlignmentOffset.y,
856 cursorInfo.secondaryCursorHeight,
857 cursorInfo.lineHeight );
861 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
864 mEventData->mUpdateCursorPosition = false;
865 mEventData->mDecoratorUpdated = true;
868 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
870 // Clamp between -space & 0 (and the text alignment).
871 if( actualSize.width > mControlSize.width )
873 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
874 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
875 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
877 mEventData->mDecoratorUpdated = true;
881 mEventData->mScrollPosition.x = 0.f;
885 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
887 // Clamp between -space & 0 (and the text alignment).
888 if( actualSize.height > mControlSize.height )
890 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
891 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
892 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
894 mEventData->mDecoratorUpdated = true;
898 mEventData->mScrollPosition.y = 0.f;
902 void Controller::Impl::ScrollToMakeCursorVisible()
904 if( NULL == mEventData )
906 // Nothing to do if there is no text input.
910 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
913 bool updateDecorator = false;
914 if( primaryCursorPosition.x < 0.f )
916 offset.x = -primaryCursorPosition.x;
917 mEventData->mScrollPosition.x += offset.x;
918 updateDecorator = true;
920 else if( primaryCursorPosition.x > mControlSize.width )
922 offset.x = mControlSize.width - primaryCursorPosition.x;
923 mEventData->mScrollPosition.x += offset.x;
924 updateDecorator = true;
927 if( updateDecorator && mEventData->mDecorator )
929 mEventData->mDecorator->UpdatePositions( offset );
932 // TODO : calculate the vertical scroll.
935 void Controller::Impl::RequestRelayout()
937 mControlInterface.RequestTextRelayout();
942 } // namespace Toolkit