From: Adeel Kazmi Date: Wed, 2 Dec 2020 22:46:22 +0000 (+0000) Subject: Further refactoring of text-controller-impl X-Git-Tag: dali_2.0.5~4^2 X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=commitdiff_plain;h=1bc80454daa9e30655e4c4dec1784f6e9e5f496a;hp=dfd3018cbcc4e2a9c61b3cd165a0bd9b538b9d0c Further refactoring of text-controller-impl Change-Id: I3f8253122378cd7f1de6afb742419a726dceefeb --- diff --git a/dali-toolkit/internal/file.list b/dali-toolkit/internal/file.list index 0081833..29ec224 100644 --- a/dali-toolkit/internal/file.list +++ b/dali-toolkit/internal/file.list @@ -152,6 +152,7 @@ SET( toolkit_src_files ${toolkit_src_dir}/text/text-io.cpp ${toolkit_src_dir}/text/text-model.cpp ${toolkit_src_dir}/text/text-scroller.cpp + ${toolkit_src_dir}/text/text-selection-handle-controller.cpp ${toolkit_src_dir}/text/text-vertical-scroller.cpp ${toolkit_src_dir}/text/text-view.cpp ${toolkit_src_dir}/text/text-view-interface.cpp diff --git a/dali-toolkit/internal/text/text-controller-impl-event-handler.cpp b/dali-toolkit/internal/text/text-controller-impl-event-handler.cpp index 7b50a5c..475f3fc 100644 --- a/dali-toolkit/internal/text/text-controller-impl-event-handler.cpp +++ b/dali-toolkit/internal/text/text-controller-impl-event-handler.cpp @@ -24,6 +24,7 @@ // INTERNAL INCLUDES #include +#include using namespace Dali; @@ -45,6 +46,225 @@ namespace Toolkit namespace Text { +bool ControllerImplEventHandler::ProcessInputEvents(Controller::Impl& impl) +{ + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" ); + + EventData*& eventData = impl.mEventData; + if( NULL == eventData ) + { + // Nothing to do if there is no text input. + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" ); + return false; + } + + if( eventData->mDecorator ) + { + for( std::vector::iterator iter = eventData->mEventQueue.begin(); + iter != eventData->mEventQueue.end(); + ++iter ) + { + switch( iter->type ) + { + case Event::CURSOR_KEY_EVENT: + { + OnCursorKeyEvent(impl, *iter); + break; + } + case Event::TAP_EVENT: + { + OnTapEvent(impl, *iter); + break; + } + case Event::LONG_PRESS_EVENT: + { + OnLongPressEvent(impl, *iter); + break; + } + case Event::PAN_EVENT: + { + OnPanEvent(impl, *iter); + break; + } + case Event::GRAB_HANDLE_EVENT: + case Event::LEFT_SELECTION_HANDLE_EVENT: + case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through + { + OnHandleEvent(impl, *iter); + break; + } + case Event::SELECT: + { + OnSelectEvent(impl, *iter); + break; + } + case Event::SELECT_ALL: + { + OnSelectAllEvent(impl); + break; + } + case Event::SELECT_NONE: + { + OnSelectNoneEvent(impl); + break; + } + } + } + } + + if( eventData->mUpdateCursorPosition || + eventData->mUpdateHighlightBox ) + { + impl.NotifyInputMethodContext(); + } + + // The cursor must also be repositioned after inserts into the model + if( eventData->mUpdateCursorPosition ) + { + // Updates the cursor position and scrolls the text to make it visible. + CursorInfo cursorInfo; + // Calculate the cursor position from the new cursor index. + impl.GetCursorPosition(eventData->mPrimaryCursorPosition, cursorInfo); + + if(nullptr != impl.mEditableControlInterface) + { + impl.mEditableControlInterface->CaretMoved( eventData->mPrimaryCursorPosition ); + } + + if( eventData->mUpdateCursorHookPosition ) + { + // Update the cursor hook position. Used to move the cursor with the keys 'up' and 'down'. + eventData->mCursorHookPositionX = cursorInfo.primaryPosition.x; + eventData->mUpdateCursorHookPosition = false; + } + + // Scroll first the text after delete ... + if( eventData->mScrollAfterDelete ) + { + impl.ScrollTextToMatchCursor(cursorInfo); + } + + // ... then, text can be scrolled to make the cursor visible. + if( eventData->mScrollAfterUpdatePosition ) + { + const Vector2 currentCursorPosition( cursorInfo.primaryPosition.x, cursorInfo.lineOffset ); + impl.ScrollToMakePositionVisible( currentCursorPosition, cursorInfo.lineHeight ); + } + eventData->mScrollAfterUpdatePosition = false; + eventData->mScrollAfterDelete = false; + + impl.UpdateCursorPosition( cursorInfo ); + + eventData->mDecoratorUpdated = true; + eventData->mUpdateCursorPosition = false; + eventData->mUpdateGrabHandlePosition = false; + } + else + { + CursorInfo leftHandleInfo; + CursorInfo rightHandleInfo; + + if( eventData->mUpdateHighlightBox ) + { + impl.GetCursorPosition(eventData->mLeftSelectionPosition, leftHandleInfo); + + impl.GetCursorPosition(eventData->mRightSelectionPosition, rightHandleInfo); + + if( eventData->mScrollAfterUpdatePosition && ( eventData->mIsLeftHandleSelected ? eventData->mUpdateLeftSelectionPosition : eventData->mUpdateRightSelectionPosition ) ) + { + if( eventData->mIsLeftHandleSelected && eventData->mIsRightHandleSelected ) + { + CursorInfo& infoLeft = leftHandleInfo; + + const Vector2 currentCursorPositionLeft( infoLeft.primaryPosition.x, infoLeft.lineOffset ); + impl.ScrollToMakePositionVisible( currentCursorPositionLeft, infoLeft.lineHeight ); + + CursorInfo& infoRight = rightHandleInfo; + + const Vector2 currentCursorPositionRight( infoRight.primaryPosition.x, infoRight.lineOffset ); + impl.ScrollToMakePositionVisible( currentCursorPositionRight, infoRight.lineHeight ); + } + else + { + CursorInfo& info = eventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo; + + const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset ); + impl. ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight ); + } + } + } + + if( eventData->mUpdateLeftSelectionPosition ) + { + impl.UpdateSelectionHandle(LEFT_SELECTION_HANDLE, leftHandleInfo); + + impl.SetPopupButtons(); + eventData->mDecoratorUpdated = true; + eventData->mUpdateLeftSelectionPosition = false; + } + + if( eventData->mUpdateRightSelectionPosition ) + { + impl.UpdateSelectionHandle(RIGHT_SELECTION_HANDLE, rightHandleInfo); + + impl.SetPopupButtons(); + eventData->mDecoratorUpdated = true; + eventData->mUpdateRightSelectionPosition = false; + } + + if( eventData->mUpdateHighlightBox ) + { + impl.RepositionSelectionHandles(); + + eventData->mUpdateLeftSelectionPosition = false; + eventData->mUpdateRightSelectionPosition = false; + eventData->mUpdateHighlightBox = false; + eventData->mIsLeftHandleSelected = false; + eventData->mIsRightHandleSelected = false; + } + + eventData->mScrollAfterUpdatePosition = false; + } + + if( eventData->mUpdateInputStyle ) + { + // Keep a copy of the current input style. + InputStyle currentInputStyle; + currentInputStyle.Copy( eventData->mInputStyle ); + + // Set the default style first. + impl.RetrieveDefaultInputStyle( eventData->mInputStyle ); + + // Get the character index from the cursor index. + const CharacterIndex styleIndex = ( eventData->mPrimaryCursorPosition > 0u ) ? eventData->mPrimaryCursorPosition - 1u : 0u; + + // Retrieve the style from the style runs stored in the logical model. + impl.mModel->mLogicalModel->RetrieveStyle( styleIndex, eventData->mInputStyle ); + + // Compare if the input style has changed. + const bool hasInputStyleChanged = !currentInputStyle.Equal( eventData->mInputStyle ); + + if( hasInputStyleChanged ) + { + const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( eventData->mInputStyle ); + // Queue the input style changed signal. + eventData->mInputStyleChangedQueue.PushBack( styleChangedMask ); + } + + eventData->mUpdateInputStyle = false; + } + + eventData->mEventQueue.clear(); + + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" ); + + const bool decoratorUpdated = eventData->mDecoratorUpdated; + eventData->mDecoratorUpdated = false; + + return decoratorUpdated; +} + + void ControllerImplEventHandler::OnCursorKeyEvent(Controller::Impl& impl, const Event& event) { if( NULL == impl.mEventData || !impl.IsShowingRealText() ) diff --git a/dali-toolkit/internal/text/text-controller-impl-event-handler.h b/dali-toolkit/internal/text/text-controller-impl-event-handler.h index 52ac94a..7399fdd 100644 --- a/dali-toolkit/internal/text/text-controller-impl-event-handler.h +++ b/dali-toolkit/internal/text/text-controller-impl-event-handler.h @@ -36,6 +36,14 @@ namespace Text struct ControllerImplEventHandler { /** + * @brief Processes input events + * + * @param[in] impl A reference to Controller::Impl + * @return True if the decorator has been updated + */ + static bool ProcessInputEvents(Controller::Impl& impl); + + /** * @brief Called by Controller::Impl when a cursor key event is received. * * @param controllerImpl A reference to Controller::Impl diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index d5edfc3..6554a2c 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -21,7 +21,6 @@ // EXTERNAL INCLUDES #include #include -#include // INTERNAL INCLUDES #include @@ -35,32 +34,17 @@ #include #include #include -#include +#include using namespace Dali; namespace { -/** - * @brief Struct used to calculate the selection box. - */ -struct SelectionBoxInfo -{ - float lineOffset; - float lineHeight; - float minX; - float maxX; -}; - #if defined(DEBUG_ENABLED) Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS"); #endif -const float MAX_FLOAT = std::numeric_limits::max(); -const float MIN_FLOAT = std::numeric_limits::min(); -const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction - #define MAKE_SHADER(A)#A const char* VERTEX_SHADER_BACKGROUND = MAKE_SHADER( @@ -165,228 +149,9 @@ EventData::EventData( DecoratorPtr decorator, InputMethodContext& inputMethodCon { } -EventData::~EventData() -{} - bool Controller::Impl::ProcessInputEvents() { - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" ); - if( NULL == mEventData ) - { - // Nothing to do if there is no text input. - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" ); - return false; - } - - if( mEventData->mDecorator ) - { - for( std::vector::iterator iter = mEventData->mEventQueue.begin(); - iter != mEventData->mEventQueue.end(); - ++iter ) - { - switch( iter->type ) - { - case Event::CURSOR_KEY_EVENT: - { - OnCursorKeyEvent( *iter ); - break; - } - case Event::TAP_EVENT: - { - OnTapEvent( *iter ); - break; - } - case Event::LONG_PRESS_EVENT: - { - OnLongPressEvent( *iter ); - break; - } - case Event::PAN_EVENT: - { - OnPanEvent( *iter ); - break; - } - case Event::GRAB_HANDLE_EVENT: - case Event::LEFT_SELECTION_HANDLE_EVENT: - case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through - { - OnHandleEvent( *iter ); - break; - } - case Event::SELECT: - { - OnSelectEvent( *iter ); - break; - } - case Event::SELECT_ALL: - { - OnSelectAllEvent(); - break; - } - case Event::SELECT_NONE: - { - OnSelectNoneEvent(); - break; - } - } - } - } - - if( mEventData->mUpdateCursorPosition || - mEventData->mUpdateHighlightBox ) - { - NotifyInputMethodContext(); - } - - // The cursor must also be repositioned after inserts into the model - if( mEventData->mUpdateCursorPosition ) - { - // Updates the cursor position and scrolls the text to make it visible. - CursorInfo cursorInfo; - // Calculate the cursor position from the new cursor index. - GetCursorPosition( mEventData->mPrimaryCursorPosition, - cursorInfo ); - - if( NULL != mEditableControlInterface ) - { - mEditableControlInterface->CaretMoved( mEventData->mPrimaryCursorPosition ); - } - - if( mEventData->mUpdateCursorHookPosition ) - { - // Update the cursor hook position. Used to move the cursor with the keys 'up' and 'down'. - mEventData->mCursorHookPositionX = cursorInfo.primaryPosition.x; - mEventData->mUpdateCursorHookPosition = false; - } - - // Scroll first the text after delete ... - if( mEventData->mScrollAfterDelete ) - { - ScrollTextToMatchCursor( cursorInfo ); - } - - // ... then, text can be scrolled to make the cursor visible. - if( mEventData->mScrollAfterUpdatePosition ) - { - const Vector2 currentCursorPosition( cursorInfo.primaryPosition.x, cursorInfo.lineOffset ); - ScrollToMakePositionVisible( currentCursorPosition, cursorInfo.lineHeight ); - } - mEventData->mScrollAfterUpdatePosition = false; - mEventData->mScrollAfterDelete = false; - - UpdateCursorPosition( cursorInfo ); - - mEventData->mDecoratorUpdated = true; - mEventData->mUpdateCursorPosition = false; - mEventData->mUpdateGrabHandlePosition = false; - } - else - { - CursorInfo leftHandleInfo; - CursorInfo rightHandleInfo; - - if( mEventData->mUpdateHighlightBox ) - { - GetCursorPosition( mEventData->mLeftSelectionPosition, - leftHandleInfo ); - - GetCursorPosition( mEventData->mRightSelectionPosition, - rightHandleInfo ); - - if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) ) - { - if( mEventData->mIsLeftHandleSelected && mEventData->mIsRightHandleSelected ) - { - CursorInfo& infoLeft = leftHandleInfo; - - const Vector2 currentCursorPositionLeft( infoLeft.primaryPosition.x, infoLeft.lineOffset ); - ScrollToMakePositionVisible( currentCursorPositionLeft, infoLeft.lineHeight ); - - CursorInfo& infoRight = rightHandleInfo; - - const Vector2 currentCursorPositionRight( infoRight.primaryPosition.x, infoRight.lineOffset ); - ScrollToMakePositionVisible( currentCursorPositionRight, infoRight.lineHeight ); - } - else - { - CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo; - - const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset ); - ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight ); - } - } - } - - if( mEventData->mUpdateLeftSelectionPosition ) - { - UpdateSelectionHandle( LEFT_SELECTION_HANDLE, - leftHandleInfo ); - - SetPopupButtons(); - mEventData->mDecoratorUpdated = true; - mEventData->mUpdateLeftSelectionPosition = false; - } - - if( mEventData->mUpdateRightSelectionPosition ) - { - UpdateSelectionHandle( RIGHT_SELECTION_HANDLE, - rightHandleInfo ); - - SetPopupButtons(); - mEventData->mDecoratorUpdated = true; - mEventData->mUpdateRightSelectionPosition = false; - } - - if( mEventData->mUpdateHighlightBox ) - { - RepositionSelectionHandles(); - - mEventData->mUpdateLeftSelectionPosition = false; - mEventData->mUpdateRightSelectionPosition = false; - mEventData->mUpdateHighlightBox = false; - mEventData->mIsLeftHandleSelected = false; - mEventData->mIsRightHandleSelected = false; - } - - mEventData->mScrollAfterUpdatePosition = false; - } - - if( mEventData->mUpdateInputStyle ) - { - // Keep a copy of the current input style. - InputStyle currentInputStyle; - currentInputStyle.Copy( mEventData->mInputStyle ); - - // Set the default style first. - RetrieveDefaultInputStyle( mEventData->mInputStyle ); - - // Get the character index from the cursor index. - const CharacterIndex styleIndex = ( mEventData->mPrimaryCursorPosition > 0u ) ? mEventData->mPrimaryCursorPosition - 1u : 0u; - - // Retrieve the style from the style runs stored in the logical model. - mModel->mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle ); - - // Compare if the input style has changed. - const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle ); - - if( hasInputStyleChanged ) - { - const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle ); - // Queue the input style changed signal. - mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask ); - } - - mEventData->mUpdateInputStyle = false; - } - - mEventData->mEventQueue.clear(); - - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" ); - - const bool decoratorUpdated = mEventData->mDecoratorUpdated; - mEventData->mDecoratorUpdated = false; - - return decoratorUpdated; + return ControllerImplEventHandler::ProcessInputEvents(*this); } void Controller::Impl::NotifyInputMethodContext() @@ -1345,46 +1110,6 @@ float Controller::Impl::GetDefaultFontLineHeight() return( fontMetrics.ascender - fontMetrics.descender ); } -void Controller::Impl::OnCursorKeyEvent( const Event& event ) -{ - ControllerImplEventHandler::OnCursorKeyEvent(*this, event); -} - -void Controller::Impl::OnTapEvent( const Event& event ) -{ - ControllerImplEventHandler::OnTapEvent(*this, event); -} - -void Controller::Impl::OnPanEvent( const Event& event ) -{ - ControllerImplEventHandler::OnPanEvent(*this, event); -} - -void Controller::Impl::OnLongPressEvent( const Event& event ) -{ - ControllerImplEventHandler::OnLongPressEvent(*this, event); -} - -void Controller::Impl::OnHandleEvent( const Event& event ) -{ - ControllerImplEventHandler::OnHandleEvent(*this, event); -} - -void Controller::Impl::OnSelectEvent( const Event& event ) -{ - ControllerImplEventHandler::OnSelectEvent(*this, event); -} - -void Controller::Impl::OnSelectAllEvent() -{ - ControllerImplEventHandler::OnSelectAllEvent(*this); -} - -void Controller::Impl::OnSelectNoneEvent() -{ - ControllerImplEventHandler::OnSelectNoneEvent(*this); -} - void Controller::Impl::SetTextSelectionRange(const uint32_t *pStart, const uint32_t *pEnd) { if( nullptr == mEventData ) @@ -1614,477 +1339,11 @@ void Controller::Impl::RequestGetTextFromClipboard() void Controller::Impl::RepositionSelectionHandles() { - CharacterIndex selectionStart = mEventData->mLeftSelectionPosition; - CharacterIndex selectionEnd = mEventData->mRightSelectionPosition; - - if( selectionStart == selectionEnd ) - { - // Nothing to select if handles are in the same place. - // So, deactive Highlight box. - mEventData->mDecorator->SetHighlightActive( false ); - return; - } - - mEventData->mDecorator->ClearHighlights(); - - const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin(); - const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin(); - const GlyphInfo* const glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin(); - const Vector2* const positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin(); - const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin(); - const CharacterIndex* const glyphToCharacterBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin(); - const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mModel->mLogicalModel->mCharacterDirections.Count() ) ? mModel->mLogicalModel->mCharacterDirections.Begin() : NULL; - - const bool isLastCharacter = selectionEnd >= mModel->mLogicalModel->mText.Count(); - const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) ); - const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) ); - - // Swap the indices if the start is greater than the end. - const bool indicesSwapped = selectionStart > selectionEnd; - - // Tell the decorator to flip the selection handles if needed. - mEventData->mDecorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection ); - - if( indicesSwapped ) - { - std::swap( selectionStart, selectionEnd ); - } - - // Get the indices to the first and last selected glyphs. - const CharacterIndex selectionEndMinusOne = selectionEnd - 1u; - const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart ); - const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne ); - const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u ); - - // Get the lines where the glyphs are laid-out. - const LineRun* lineRun = mModel->mVisualModel->mLines.Begin(); - - LineIndex lineIndex = 0u; - Length numberOfLines = 0u; - mModel->mVisualModel->GetNumberOfLines( glyphStart, - 1u + glyphEnd - glyphStart, - lineIndex, - numberOfLines ); - const LineIndex firstLineIndex = lineIndex; - - // Create the structure to store some selection box info. - Vector selectionBoxLinesInfo; - selectionBoxLinesInfo.Resize( numberOfLines ); - - SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin(); - selectionBoxInfo->minX = MAX_FLOAT; - selectionBoxInfo->maxX = MIN_FLOAT; - - // Keep the min and max 'x' position to calculate the size and position of the highlighed text. - float minHighlightX = std::numeric_limits::max(); - float maxHighlightX = std::numeric_limits::min(); - Size highLightSize; - Vector2 highLightPosition; // The highlight position in decorator's coords. - - // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph. - - // The line's vertical offset of all the lines before the line where the first glyph is laid-out. - selectionBoxInfo->lineOffset = CalculateLineOffset( mModel->mVisualModel->mLines, - firstLineIndex ); - - // Transform to decorator's (control) coords. - selectionBoxInfo->lineOffset += mModel->mScrollPosition.y; - - lineRun += firstLineIndex; - - // The line height is the addition of the line ascender and the line descender. - // However, the line descender has a negative value, hence the subtraction. - selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender; - - GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u; - - // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code. - const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart ); - bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionStart ) ); - - // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code. - const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd ); - bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mModel->mLogicalModel->GetScript( selectionEndMinusOne ) ); - - // The number of quads of the selection box. - const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u ); - mEventData->mDecorator->ResizeHighlightQuads( numberOfQuads ); - - // Count the actual number of quads. - unsigned int actualNumberOfQuads = 0u; - Vector4 quad; - - // Traverse the glyphs. - for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index ) - { - const GlyphInfo& glyph = *( glyphsBuffer + index ); - const Vector2& position = *( positionsBuffer + index ); - - if( splitStartGlyph ) - { - // If the first glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box. - - const float glyphAdvance = glyph.advance / static_cast( numberOfCharactersStart ); - const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart ); - // Get the direction of the character. - CharacterDirection isCurrentRightToLeft = false; - if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right. - { - isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart ); - } - - // The end point could be in the middle of the ligature. - // Calculate the number of characters selected. - const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex ); - - quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + glyphAdvance * static_cast( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex ); - quad.y = selectionBoxInfo->lineOffset; - quad.z = quad.x + static_cast( numberOfCharacters ) * glyphAdvance; - quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight; - - // Store the min and max 'x' for each line. - selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x ); - selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z ); - - mEventData->mDecorator->AddHighlight( actualNumberOfQuads, quad ); - ++actualNumberOfQuads; - - splitStartGlyph = false; - continue; - } - - if( splitEndGlyph && ( index == glyphEnd ) ) - { - // Equally, if the last glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box. - - const float glyphAdvance = glyph.advance / static_cast( numberOfCharactersEnd ); - const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd ); - // Get the direction of the character. - CharacterDirection isCurrentRightToLeft = false; - if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right. - { - isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd ); - } - - const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex; - - quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast( numberOfCharacters ) ) : 0.f ); - quad.y = selectionBoxInfo->lineOffset; - quad.z = quad.x + static_cast( interGlyphIndex ) * glyphAdvance; - quad.w = quad.y + selectionBoxInfo->lineHeight; - - // Store the min and max 'x' for each line. - selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x ); - selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z ); - - mEventData->mDecorator->AddHighlight( actualNumberOfQuads, - quad ); - ++actualNumberOfQuads; - - splitEndGlyph = false; - continue; - } - - quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + mModel->mScrollPosition.x; - quad.y = selectionBoxInfo->lineOffset; - quad.z = quad.x + glyph.advance; - quad.w = quad.y + selectionBoxInfo->lineHeight; - - // Store the min and max 'x' for each line. - selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x ); - selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z ); - - mEventData->mDecorator->AddHighlight( actualNumberOfQuads, - quad ); - ++actualNumberOfQuads; - - // Whether to retrieve the next line. - if( index == lastGlyphOfLine ) - { - ++lineIndex; - if( lineIndex < firstLineIndex + numberOfLines ) - { - // Retrieve the next line. - ++lineRun; - - // Get the last glyph of the new line. - lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u; - - // Keep the offset and height of the current selection box. - const float currentLineOffset = selectionBoxInfo->lineOffset; - const float currentLineHeight = selectionBoxInfo->lineHeight; - - // Get the selection box info for the next line. - ++selectionBoxInfo; - - selectionBoxInfo->minX = MAX_FLOAT; - selectionBoxInfo->maxX = MIN_FLOAT; - - // Update the line's vertical offset. - selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight; - - // The line height is the addition of the line ascender and the line descender. - // However, the line descender has a negative value, hence the subtraction. - selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender; - } - } - } - - // Traverses all the lines and updates the min and max 'x' positions and the total height. - // The final width is calculated after 'boxifying' the selection. - for( Vector::ConstIterator it = selectionBoxLinesInfo.Begin(), - endIt = selectionBoxLinesInfo.End(); - it != endIt; - ++it ) - { - const SelectionBoxInfo& info = *it; - - // Update the size of the highlighted text. - highLightSize.height += info.lineHeight; - minHighlightX = std::min( minHighlightX, info.minX ); - maxHighlightX = std::max( maxHighlightX, info.maxX ); - } - - // Add extra geometry to 'boxify' the selection. - - if( 1u < numberOfLines ) - { - // Boxify the first line. - lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex; - const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() ); - - bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection ); - bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection ); - - if( boxifyBegin ) - { - quad.x = 0.f; - quad.y = firstSelectionBoxLineInfo.lineOffset; - quad.z = firstSelectionBoxLineInfo.minX; - quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight; - - // Boxify at the beginning of the line. - mEventData->mDecorator->AddHighlight( actualNumberOfQuads, - quad ); - ++actualNumberOfQuads; - - // Update the size of the highlighted text. - minHighlightX = 0.f; - } - - if( boxifyEnd ) - { - quad.x = firstSelectionBoxLineInfo.maxX; - quad.y = firstSelectionBoxLineInfo.lineOffset; - quad.z = mModel->mVisualModel->mControlSize.width; - quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight; - - // Boxify at the end of the line. - mEventData->mDecorator->AddHighlight( actualNumberOfQuads, - quad ); - ++actualNumberOfQuads; - - // Update the size of the highlighted text. - maxHighlightX = mModel->mVisualModel->mControlSize.width; - } - - // Boxify the central lines. - if( 2u < numberOfLines ) - { - for( Vector::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u, - endIt = selectionBoxLinesInfo.End() - 1u; - it != endIt; - ++it ) - { - const SelectionBoxInfo& info = *it; - - quad.x = 0.f; - quad.y = info.lineOffset; - quad.z = info.minX; - quad.w = info.lineOffset + info.lineHeight; - - mEventData->mDecorator->AddHighlight( actualNumberOfQuads, - quad ); - ++actualNumberOfQuads; - - quad.x = info.maxX; - quad.y = info.lineOffset; - quad.z = mModel->mVisualModel->mControlSize.width; - quad.w = info.lineOffset + info.lineHeight; - - mEventData->mDecorator->AddHighlight( actualNumberOfQuads, - quad ); - ++actualNumberOfQuads; - } - - // Update the size of the highlighted text. - minHighlightX = 0.f; - maxHighlightX = mModel->mVisualModel->mControlSize.width; - } - - // Boxify the last line. - lineRun = mModel->mVisualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u; - const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u ); - - boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection ); - boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection ); - - if( boxifyBegin ) - { - quad.x = 0.f; - quad.y = lastSelectionBoxLineInfo.lineOffset; - quad.z = lastSelectionBoxLineInfo.minX; - quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight; - - // Boxify at the beginning of the line. - mEventData->mDecorator->AddHighlight( actualNumberOfQuads, - quad ); - ++actualNumberOfQuads; - - // Update the size of the highlighted text. - minHighlightX = 0.f; - } - - if( boxifyEnd ) - { - quad.x = lastSelectionBoxLineInfo.maxX; - quad.y = lastSelectionBoxLineInfo.lineOffset; - quad.z = mModel->mVisualModel->mControlSize.width; - quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight; - - // Boxify at the end of the line. - mEventData->mDecorator->AddHighlight( actualNumberOfQuads, - quad ); - ++actualNumberOfQuads; - - // Update the size of the highlighted text. - maxHighlightX = mModel->mVisualModel->mControlSize.width; - } - } - - // Set the actual number of quads. - mEventData->mDecorator->ResizeHighlightQuads( actualNumberOfQuads ); - - // Sets the highlight's size and position. In decorator's coords. - // The highlight's height has been calculated above (before 'boxifying' the highlight). - highLightSize.width = maxHighlightX - minHighlightX; - - highLightPosition.x = minHighlightX; - const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() ); - highLightPosition.y = firstSelectionBoxLineInfo.lineOffset; - - mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize, static_cast( mModel->GetOutlineWidth() ) ); - - if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() ) - { - CursorInfo primaryCursorInfo; - GetCursorPosition( mEventData->mLeftSelectionPosition, - primaryCursorInfo ); - - const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + mModel->mScrollPosition; - - mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, - primaryPosition.x, - primaryCursorInfo.lineOffset + mModel->mScrollPosition.y, - primaryCursorInfo.lineHeight ); - - CursorInfo secondaryCursorInfo; - GetCursorPosition( mEventData->mRightSelectionPosition, - secondaryCursorInfo ); - - const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + mModel->mScrollPosition; - - mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, - secondaryPosition.x, - secondaryCursorInfo.lineOffset + mModel->mScrollPosition.y, - secondaryCursorInfo.lineHeight ); - } - - // Set the flag to update the decorator. - mEventData->mDecoratorUpdated = true; + SelectionHandleController::Reposition(*this); } - void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action ) { - if( NULL == mEventData ) - { - // Nothing to do if there is no text input. - return; - } - - if( IsShowingPlaceholderText() ) - { - // Nothing to do if there is the place-holder text. - return; - } - - const Length numberOfGlyphs = mModel->mVisualModel->mGlyphs.Count(); - const Length numberOfLines = mModel->mVisualModel->mLines.Count(); - if( ( 0 == numberOfGlyphs ) || - ( 0 == numberOfLines ) ) - { - // Nothing to do if there is no text. - return; - } - - // Find which word was selected - CharacterIndex selectionStart( 0 ); - CharacterIndex selectionEnd( 0 ); - CharacterIndex noTextHitIndex( 0 ); - const bool characterHit = FindSelectionIndices( mModel->mVisualModel, - mModel->mLogicalModel, - mMetrics, - visualX, - visualY, - selectionStart, - selectionEnd, - noTextHitIndex ); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd ); - - if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) ) - { - ChangeState( EventData::SELECTING ); - - mEventData->mLeftSelectionPosition = selectionStart; - mEventData->mRightSelectionPosition = selectionEnd; - - mEventData->mUpdateLeftSelectionPosition = true; - mEventData->mUpdateRightSelectionPosition = true; - mEventData->mUpdateHighlightBox = true; - - // It may happen an InputMethodContext commit event arrives before the selection event - // if the InputMethodContext is in pre-edit state. The commit event will set the - // mEventData->mUpdateCursorPosition flag to true. If it's not set back - // to false, the highlight box won't be updated. - mEventData->mUpdateCursorPosition = false; - - mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ); - - // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection - mEventData->mPrimaryCursorPosition = std::max( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition ); - } - else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action ) - { - // Nothing to select. i.e. a white space, out of bounds - ChangeState( EventData::EDITING_WITH_POPUP ); - - mEventData->mPrimaryCursorPosition = noTextHitIndex; - - mEventData->mUpdateCursorPosition = true; - mEventData->mUpdateGrabHandlePosition = true; - mEventData->mScrollAfterUpdatePosition = true; - mEventData->mUpdateInputStyle = true; - } - else if( Controller::NoTextTap::NO_ACTION == action ) - { - // Nothing to select. i.e. a white space, out of bounds - mEventData->mPrimaryCursorPosition = noTextHitIndex; - - mEventData->mUpdateCursorPosition = true; - mEventData->mUpdateGrabHandlePosition = true; - mEventData->mScrollAfterUpdatePosition = true; - mEventData->mUpdateInputStyle = true; - } + SelectionHandleController::Reposition(*this, visualX, visualY, action); } void Controller::Impl::SetPopupButtons() @@ -2582,24 +1841,7 @@ void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo ) void Controller::Impl::UpdateSelectionHandle( HandleType handleType, const CursorInfo& cursorInfo ) { - if( ( LEFT_SELECTION_HANDLE != handleType ) && - ( RIGHT_SELECTION_HANDLE != handleType ) ) - { - return; - } - - const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition; - - // Sets the handle's position. - mEventData->mDecorator->SetPosition( handleType, - cursorPosition.x, - cursorInfo.lineOffset + mModel->mScrollPosition.y, - cursorInfo.lineHeight ); - - // If selection handle at start of the text and other at end of the text then all text is selected. - const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition ); - const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition ); - mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mModel->mLogicalModel->mText.Count() ); + SelectionHandleController::Update(*this, handleType, cursorInfo); } void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize ) diff --git a/dali-toolkit/internal/text/text-controller-impl.h b/dali-toolkit/internal/text/text-controller-impl.h index 63a897c..db02721 100755 --- a/dali-toolkit/internal/text/text-controller-impl.h +++ b/dali-toolkit/internal/text/text-controller-impl.h @@ -49,6 +49,7 @@ const float DEFAULT_FONT_SIZE_SCALE = 1.f; struct CursorInfo; struct FontDefaults; struct ControllerImplEventHandler; +struct SelectionHandleController; class SelectableControlInterface; @@ -109,7 +110,7 @@ struct EventData EventData( DecoratorPtr decorator, InputMethodContext& inputMethodContext ); - ~EventData(); + ~EventData() = default; static bool IsEditingState( State stateToCheck ) { @@ -174,7 +175,7 @@ struct EventData bool mPlaceholderEllipsisFlag : 1; ///< True if the text controller sets the placeholder ellipsis. bool mShiftSelectionFlag : 1; ///< True if the text selection using Shift key is enabled. bool mUpdateAlignment : 1; ///< True if the whole text needs to be full aligned.. - bool mEditingEnabled : 1; ///< True if the editing is enabled, false otherwise. + bool mEditingEnabled : 1; ///< True if the editing is enabled, false otherwise. }; struct ModifyEvent @@ -614,22 +615,6 @@ struct Controller::Impl */ float GetDefaultFontLineHeight(); - void OnCursorKeyEvent( const Event& event ); - - void OnTapEvent( const Event& event ); - - void OnPanEvent( const Event& event ); - - void OnLongPressEvent( const Event& event ); - - void OnHandleEvent( const Event& event ); - - void OnSelectEvent( const Event& event ); - - void OnSelectAllEvent(); - - void OnSelectNoneEvent(); - /** * @copydoc Text::Controller::GetPrimaryCursorPosition() */ @@ -849,6 +834,7 @@ public: private: friend ControllerImplEventHandler; + friend SelectionHandleController; }; } // namespace Text diff --git a/dali-toolkit/internal/text/text-selection-handle-controller.cpp b/dali-toolkit/internal/text/text-selection-handle-controller.cpp new file mode 100644 index 0000000..664b59e --- /dev/null +++ b/dali-toolkit/internal/text/text-selection-handle-controller.cpp @@ -0,0 +1,574 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// CLASS HEADER +#include + +#include +#include + +// INTERNAL INCLUDES +#include +#include + +using namespace Dali; + +namespace +{ + +/** + * @brief Struct used to calculate the selection box. + */ +struct SelectionBoxInfo +{ + float lineOffset; + float lineHeight; + float minX; + float maxX; +}; + +#if defined(DEBUG_ENABLED) +Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS"); +#endif + +const float MAX_FLOAT = std::numeric_limits::max(); +const float MIN_FLOAT = std::numeric_limits::min(); +const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction + +} // namespace + +namespace Dali +{ + +namespace Toolkit +{ + +namespace Text +{ + +void SelectionHandleController::Reposition(Controller::Impl& impl) +{ + EventData*& eventData = impl.mEventData; + + CharacterIndex selectionStart = eventData->mLeftSelectionPosition; + CharacterIndex selectionEnd = eventData->mRightSelectionPosition; + + DecoratorPtr& decorator = eventData->mDecorator; + + if( selectionStart == selectionEnd ) + { + // Nothing to select if handles are in the same place. + // So, deactive Highlight box. + decorator->SetHighlightActive( false ); + return; + } + + decorator->ClearHighlights(); + + ModelPtr& model = impl.mModel; + VisualModelPtr& visualModel = model->mVisualModel; + LogicalModelPtr& logicalModel = model->mLogicalModel; + + const GlyphIndex* const charactersToGlyphBuffer = visualModel->mCharactersToGlyph.Begin(); + const Length* const glyphsPerCharacterBuffer = visualModel->mGlyphsPerCharacter.Begin(); + const GlyphInfo* const glyphsBuffer = visualModel->mGlyphs.Begin(); + const Vector2* const positionsBuffer = visualModel->mGlyphPositions.Begin(); + const Length* const charactersPerGlyphBuffer = visualModel->mCharactersPerGlyph.Begin(); + const CharacterIndex* const glyphToCharacterBuffer = visualModel->mGlyphsToCharacters.Begin(); + const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != logicalModel->mCharacterDirections.Count() ) ? logicalModel->mCharacterDirections.Begin() : NULL; + + const bool isLastCharacter = selectionEnd >= logicalModel->mText.Count(); + const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) ); + const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) ); + + // Swap the indices if the start is greater than the end. + const bool indicesSwapped = selectionStart > selectionEnd; + + // Tell the decorator to flip the selection handles if needed. + decorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection ); + + if( indicesSwapped ) + { + std::swap( selectionStart, selectionEnd ); + } + + // Get the indices to the first and last selected glyphs. + const CharacterIndex selectionEndMinusOne = selectionEnd - 1u; + const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart ); + const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne ); + const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u ); + + // Get the lines where the glyphs are laid-out. + const LineRun* lineRun = visualModel->mLines.Begin(); + + LineIndex lineIndex = 0u; + Length numberOfLines = 0u; + visualModel->GetNumberOfLines( glyphStart, + 1u + glyphEnd - glyphStart, + lineIndex, + numberOfLines ); + const LineIndex firstLineIndex = lineIndex; + + // Create the structure to store some selection box info. + Vector selectionBoxLinesInfo; + selectionBoxLinesInfo.Resize( numberOfLines ); + + SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin(); + selectionBoxInfo->minX = MAX_FLOAT; + selectionBoxInfo->maxX = MIN_FLOAT; + + // Keep the min and max 'x' position to calculate the size and position of the highlighed text. + float minHighlightX = std::numeric_limits::max(); + float maxHighlightX = std::numeric_limits::min(); + Size highLightSize; + Vector2 highLightPosition; // The highlight position in decorator's coords. + + // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph. + + // The line's vertical offset of all the lines before the line where the first glyph is laid-out. + selectionBoxInfo->lineOffset = CalculateLineOffset( visualModel->mLines, + firstLineIndex ); + + // Transform to decorator's (control) coords. + selectionBoxInfo->lineOffset += model->mScrollPosition.y; + + lineRun += firstLineIndex; + + // The line height is the addition of the line ascender and the line descender. + // However, the line descender has a negative value, hence the subtraction. + selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender; + + GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u; + + // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code. + const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart ); + bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( logicalModel->GetScript( selectionStart ) ); + + // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code. + const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd ); + bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( logicalModel->GetScript( selectionEndMinusOne ) ); + + // The number of quads of the selection box. + const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u ); + decorator->ResizeHighlightQuads( numberOfQuads ); + + // Count the actual number of quads. + unsigned int actualNumberOfQuads = 0u; + Vector4 quad; + + // Traverse the glyphs. + for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index ) + { + const GlyphInfo& glyph = *( glyphsBuffer + index ); + const Vector2& position = *( positionsBuffer + index ); + + if( splitStartGlyph ) + { + // If the first glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box. + + const float glyphAdvance = glyph.advance / static_cast( numberOfCharactersStart ); + const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart ); + // Get the direction of the character. + CharacterDirection isCurrentRightToLeft = false; + if( nullptr != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right. + { + isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart ); + } + + // The end point could be in the middle of the ligature. + // Calculate the number of characters selected. + const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex ); + + quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + model->mScrollPosition.x + glyphAdvance * static_cast( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex ); + quad.y = selectionBoxInfo->lineOffset; + quad.z = quad.x + static_cast( numberOfCharacters ) * glyphAdvance; + quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight; + + // Store the min and max 'x' for each line. + selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x ); + selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z ); + + decorator->AddHighlight( actualNumberOfQuads, quad ); + ++actualNumberOfQuads; + + splitStartGlyph = false; + continue; + } + + if( splitEndGlyph && ( index == glyphEnd ) ) + { + // Equally, if the last glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box. + + const float glyphAdvance = glyph.advance / static_cast( numberOfCharactersEnd ); + const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd ); + // Get the direction of the character. + CharacterDirection isCurrentRightToLeft = false; + if( nullptr != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right. + { + isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd ); + } + + const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex; + + quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + model->mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast( numberOfCharacters ) ) : 0.f ); + quad.y = selectionBoxInfo->lineOffset; + quad.z = quad.x + static_cast( interGlyphIndex ) * glyphAdvance; + quad.w = quad.y + selectionBoxInfo->lineHeight; + + // Store the min and max 'x' for each line. + selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x ); + selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z ); + + decorator->AddHighlight( actualNumberOfQuads, + quad ); + ++actualNumberOfQuads; + + splitEndGlyph = false; + continue; + } + + quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + model->mScrollPosition.x; + quad.y = selectionBoxInfo->lineOffset; + quad.z = quad.x + glyph.advance; + quad.w = quad.y + selectionBoxInfo->lineHeight; + + // Store the min and max 'x' for each line. + selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x ); + selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z ); + + decorator->AddHighlight( actualNumberOfQuads, + quad ); + ++actualNumberOfQuads; + + // Whether to retrieve the next line. + if( index == lastGlyphOfLine ) + { + ++lineIndex; + if( lineIndex < firstLineIndex + numberOfLines ) + { + // Retrieve the next line. + ++lineRun; + + // Get the last glyph of the new line. + lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u; + + // Keep the offset and height of the current selection box. + const float currentLineOffset = selectionBoxInfo->lineOffset; + const float currentLineHeight = selectionBoxInfo->lineHeight; + + // Get the selection box info for the next line. + ++selectionBoxInfo; + + selectionBoxInfo->minX = MAX_FLOAT; + selectionBoxInfo->maxX = MIN_FLOAT; + + // Update the line's vertical offset. + selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight; + + // The line height is the addition of the line ascender and the line descender. + // However, the line descender has a negative value, hence the subtraction. + selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender; + } + } + } + + // Traverses all the lines and updates the min and max 'x' positions and the total height. + // The final width is calculated after 'boxifying' the selection. + for( Vector::ConstIterator it = selectionBoxLinesInfo.Begin(), + endIt = selectionBoxLinesInfo.End(); + it != endIt; + ++it ) + { + const SelectionBoxInfo& info = *it; + + // Update the size of the highlighted text. + highLightSize.height += info.lineHeight; + minHighlightX = std::min( minHighlightX, info.minX ); + maxHighlightX = std::max( maxHighlightX, info.maxX ); + } + + // Add extra geometry to 'boxify' the selection. + + if( 1u < numberOfLines ) + { + // Boxify the first line. + lineRun = visualModel->mLines.Begin() + firstLineIndex; + const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() ); + + bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection ); + bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection ); + + if( boxifyBegin ) + { + quad.x = 0.f; + quad.y = firstSelectionBoxLineInfo.lineOffset; + quad.z = firstSelectionBoxLineInfo.minX; + quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight; + + // Boxify at the beginning of the line. + decorator->AddHighlight( actualNumberOfQuads, + quad ); + ++actualNumberOfQuads; + + // Update the size of the highlighted text. + minHighlightX = 0.f; + } + + if( boxifyEnd ) + { + quad.x = firstSelectionBoxLineInfo.maxX; + quad.y = firstSelectionBoxLineInfo.lineOffset; + quad.z = visualModel->mControlSize.width; + quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight; + + // Boxify at the end of the line. + decorator->AddHighlight( actualNumberOfQuads, + quad ); + ++actualNumberOfQuads; + + // Update the size of the highlighted text. + maxHighlightX = visualModel->mControlSize.width; + } + + // Boxify the central lines. + if( 2u < numberOfLines ) + { + for( Vector::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u, + endIt = selectionBoxLinesInfo.End() - 1u; + it != endIt; + ++it ) + { + const SelectionBoxInfo& info = *it; + + quad.x = 0.f; + quad.y = info.lineOffset; + quad.z = info.minX; + quad.w = info.lineOffset + info.lineHeight; + + decorator->AddHighlight( actualNumberOfQuads, + quad ); + ++actualNumberOfQuads; + + quad.x = info.maxX; + quad.y = info.lineOffset; + quad.z = visualModel->mControlSize.width; + quad.w = info.lineOffset + info.lineHeight; + + decorator->AddHighlight( actualNumberOfQuads, + quad ); + ++actualNumberOfQuads; + } + + // Update the size of the highlighted text. + minHighlightX = 0.f; + maxHighlightX = visualModel->mControlSize.width; + } + + // Boxify the last line. + lineRun = visualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u; + const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u ); + + boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection ); + boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection ); + + if( boxifyBegin ) + { + quad.x = 0.f; + quad.y = lastSelectionBoxLineInfo.lineOffset; + quad.z = lastSelectionBoxLineInfo.minX; + quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight; + + // Boxify at the beginning of the line. + decorator->AddHighlight( actualNumberOfQuads, + quad ); + ++actualNumberOfQuads; + + // Update the size of the highlighted text. + minHighlightX = 0.f; + } + + if( boxifyEnd ) + { + quad.x = lastSelectionBoxLineInfo.maxX; + quad.y = lastSelectionBoxLineInfo.lineOffset; + quad.z = visualModel->mControlSize.width; + quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight; + + // Boxify at the end of the line. + decorator->AddHighlight(actualNumberOfQuads, quad); + ++actualNumberOfQuads; + + // Update the size of the highlighted text. + maxHighlightX = visualModel->mControlSize.width; + } + } + + // Set the actual number of quads. + decorator->ResizeHighlightQuads( actualNumberOfQuads ); + + // Sets the highlight's size and position. In decorator's coords. + // The highlight's height has been calculated above (before 'boxifying' the highlight). + highLightSize.width = maxHighlightX - minHighlightX; + + highLightPosition.x = minHighlightX; + const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() ); + highLightPosition.y = firstSelectionBoxLineInfo.lineOffset; + + decorator->SetHighLightBox( highLightPosition, highLightSize, static_cast( model->GetOutlineWidth() ) ); + + if( !decorator->IsSmoothHandlePanEnabled() ) + { + CursorInfo primaryCursorInfo; + impl.GetCursorPosition(eventData->mLeftSelectionPosition, primaryCursorInfo); + + const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + model->mScrollPosition; + + decorator->SetPosition(LEFT_SELECTION_HANDLE, + primaryPosition.x, + primaryCursorInfo.lineOffset + model->mScrollPosition.y, + primaryCursorInfo.lineHeight ); + + CursorInfo secondaryCursorInfo; + impl.GetCursorPosition(eventData->mRightSelectionPosition, secondaryCursorInfo); + + const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + model->mScrollPosition; + + decorator->SetPosition(RIGHT_SELECTION_HANDLE, + secondaryPosition.x, + secondaryCursorInfo.lineOffset + model->mScrollPosition.y, + secondaryCursorInfo.lineHeight ); + } + + // Set the flag to update the decorator. + eventData->mDecoratorUpdated = true; +} + +void SelectionHandleController::Reposition(Controller::Impl& impl, float visualX, float visualY, Controller::NoTextTap::Action action) +{ + EventData*& eventData = impl.mEventData; + if(nullptr == eventData) + { + // Nothing to do if there is no text input. + return; + } + + if(impl.IsShowingPlaceholderText()) + { + // Nothing to do if there is the place-holder text. + return; + } + + ModelPtr& model = impl.mModel; + VisualModelPtr& visualModel = model->mVisualModel; + const Length numberOfGlyphs = visualModel->mGlyphs.Count(); + const Length numberOfLines = visualModel->mLines.Count(); + if( ( 0 == numberOfGlyphs ) || + ( 0 == numberOfLines ) ) + { + // Nothing to do if there is no text. + return; + } + + // Find which word was selected + CharacterIndex selectionStart( 0 ); + CharacterIndex selectionEnd( 0 ); + CharacterIndex noTextHitIndex( 0 ); + const bool characterHit = FindSelectionIndices( visualModel, + model->mLogicalModel, + impl.mMetrics, + visualX, + visualY, + selectionStart, + selectionEnd, + noTextHitIndex ); + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", &impl, selectionStart, selectionEnd ); + + if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) ) + { + impl.ChangeState( EventData::SELECTING ); + + eventData->mLeftSelectionPosition = selectionStart; + eventData->mRightSelectionPosition = selectionEnd; + + eventData->mUpdateLeftSelectionPosition = true; + eventData->mUpdateRightSelectionPosition = true; + eventData->mUpdateHighlightBox = true; + + // It may happen an InputMethodContext commit event arrives before the selection event + // if the InputMethodContext is in pre-edit state. The commit event will set the + // eventData->mUpdateCursorPosition flag to true. If it's not set back + // to false, the highlight box won't be updated. + eventData->mUpdateCursorPosition = false; + + eventData->mScrollAfterUpdatePosition = ( eventData->mLeftSelectionPosition != eventData->mRightSelectionPosition ); + + // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection + eventData->mPrimaryCursorPosition = std::max( eventData->mLeftSelectionPosition, eventData->mRightSelectionPosition ); + } + else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action ) + { + // Nothing to select. i.e. a white space, out of bounds + impl.ChangeState( EventData::EDITING_WITH_POPUP ); + + eventData->mPrimaryCursorPosition = noTextHitIndex; + + eventData->mUpdateCursorPosition = true; + eventData->mUpdateGrabHandlePosition = true; + eventData->mScrollAfterUpdatePosition = true; + eventData->mUpdateInputStyle = true; + } + else if( Controller::NoTextTap::NO_ACTION == action ) + { + // Nothing to select. i.e. a white space, out of bounds + eventData->mPrimaryCursorPosition = noTextHitIndex; + + eventData->mUpdateCursorPosition = true; + eventData->mUpdateGrabHandlePosition = true; + eventData->mScrollAfterUpdatePosition = true; + eventData->mUpdateInputStyle = true; + } +} + +void SelectionHandleController::Update(Controller::Impl& impl, HandleType handleType, const CursorInfo& cursorInfo) +{ + if( ( LEFT_SELECTION_HANDLE != handleType ) && + ( RIGHT_SELECTION_HANDLE != handleType ) ) + { + return; + } + + ModelPtr& model = impl.mModel; + const Vector2 cursorPosition = cursorInfo.primaryPosition + model->mScrollPosition; + + // Sets the handle's position. + EventData*& eventData = impl.mEventData; + eventData->mDecorator->SetPosition(handleType, + cursorPosition.x, + cursorInfo.lineOffset + model->mScrollPosition.y, + cursorInfo.lineHeight ); + + // If selection handle at start of the text and other at end of the text then all text is selected. + const CharacterIndex startOfSelection = std::min( eventData->mLeftSelectionPosition, eventData->mRightSelectionPosition ); + const CharacterIndex endOfSelection = std::max ( eventData->mLeftSelectionPosition, eventData->mRightSelectionPosition ); + eventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == model->mLogicalModel->mText.Count() ); +} + +} // namespace Text + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/internal/text/text-selection-handle-controller.h b/dali-toolkit/internal/text/text-selection-handle-controller.h new file mode 100644 index 0000000..2988c85 --- /dev/null +++ b/dali-toolkit/internal/text/text-selection-handle-controller.h @@ -0,0 +1,59 @@ +#ifndef DALI_TOOLKIT_TEXT_SELECTION_HANDLE_CONTROLLER_H +#define DALI_TOOLKIT_TEXT_SELECTION_HANDLE_CONTROLLER_H + +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// INTERNAL INCLUDES +#include +#include +#include + +namespace Dali +{ + +namespace Toolkit +{ + +namespace Text +{ + +/** + * @brief Updates the Selection Handles. + */ +struct SelectionHandleController +{ + /// @copydoc Controller::Impl::RepositionSelectionHandles + /// @param[in] impl The Text controller Impl. + static void Reposition(Controller::Impl& impl); + + /// @copydoc Controller::Impl::RepositionSelectionHandles + /// @param[in] impl The Text controller Impl. + static void Reposition(Controller::Impl& impl, float visualX, float visualY, Controller::NoTextTap::Action action); + + /// @copydoc Controller::Impl::UpdateSelectionHandle + /// @param[in] impl The Text controller Impl. + static void Update(Controller::Impl& impl, HandleType handleType, const CursorInfo& cursorInfo); +}; + +} // namespace Text + +} // namespace Toolkit + +} // namespace Dali + +#endif // DALI_TOOLKIT_TEXT_SELECTION_HANDLE_CONTROLLER_H