2 * Copyright (c) 2020 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-selection-handle-controller.h>
21 #include <dali/integration-api/debug.h>
25 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
26 #include <dali-toolkit/internal/text/text-controller-impl-event-handler.h>
34 * @brief Struct used to calculate the selection box.
36 struct SelectionBoxInfo
44 #if defined(DEBUG_ENABLED)
45 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
48 const float MAX_FLOAT = std::numeric_limits<float>::max();
49 const float MIN_FLOAT = std::numeric_limits<float>::min();
50 const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction
63 void SelectionHandleController::Reposition(Controller::Impl& impl)
65 EventData*& eventData = impl.mEventData;
67 CharacterIndex selectionStart = eventData->mLeftSelectionPosition;
68 CharacterIndex selectionEnd = eventData->mRightSelectionPosition;
70 DecoratorPtr& decorator = eventData->mDecorator;
72 if( selectionStart == selectionEnd )
74 // Nothing to select if handles are in the same place.
75 // So, deactive Highlight box.
76 decorator->SetHighlightActive( false );
80 decorator->ClearHighlights();
82 ModelPtr& model = impl.mModel;
83 VisualModelPtr& visualModel = model->mVisualModel;
84 LogicalModelPtr& logicalModel = model->mLogicalModel;
86 const GlyphIndex* const charactersToGlyphBuffer = visualModel->mCharactersToGlyph.Begin();
87 const Length* const glyphsPerCharacterBuffer = visualModel->mGlyphsPerCharacter.Begin();
88 const GlyphInfo* const glyphsBuffer = visualModel->mGlyphs.Begin();
89 const Vector2* const positionsBuffer = visualModel->mGlyphPositions.Begin();
90 const Length* const charactersPerGlyphBuffer = visualModel->mCharactersPerGlyph.Begin();
91 const CharacterIndex* const glyphToCharacterBuffer = visualModel->mGlyphsToCharacters.Begin();
92 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != logicalModel->mCharacterDirections.Count() ) ? logicalModel->mCharacterDirections.Begin() : NULL;
94 const bool isLastCharacter = selectionEnd >= logicalModel->mText.Count();
95 const CharacterDirection startDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + selectionStart ) );
96 const CharacterDirection endDirection = ( ( NULL == modelCharacterDirectionsBuffer ) ? false : *( modelCharacterDirectionsBuffer + ( selectionEnd - ( isLastCharacter ? 1u : 0u ) ) ) );
98 // Swap the indices if the start is greater than the end.
99 const bool indicesSwapped = selectionStart > selectionEnd;
101 // Tell the decorator to flip the selection handles if needed.
102 decorator->SetSelectionHandleFlipState( indicesSwapped, startDirection, endDirection );
106 std::swap( selectionStart, selectionEnd );
109 // Get the indices to the first and last selected glyphs.
110 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
111 const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
112 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne );
113 const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
115 // Get the lines where the glyphs are laid-out.
116 const LineRun* lineRun = visualModel->mLines.Begin();
118 LineIndex lineIndex = 0u;
119 Length numberOfLines = 0u;
120 visualModel->GetNumberOfLines( glyphStart,
121 1u + glyphEnd - glyphStart,
124 const LineIndex firstLineIndex = lineIndex;
126 // Create the structure to store some selection box info.
127 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
128 selectionBoxLinesInfo.Resize( numberOfLines );
130 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
131 selectionBoxInfo->minX = MAX_FLOAT;
132 selectionBoxInfo->maxX = MIN_FLOAT;
134 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
135 float minHighlightX = std::numeric_limits<float>::max();
136 float maxHighlightX = std::numeric_limits<float>::min();
138 Vector2 highLightPosition; // The highlight position in decorator's coords.
140 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
142 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
143 selectionBoxInfo->lineOffset = CalculateLineOffset( visualModel->mLines,
146 // Transform to decorator's (control) coords.
147 selectionBoxInfo->lineOffset += model->mScrollPosition.y;
149 lineRun += firstLineIndex;
151 // The line height is the addition of the line ascender and the line descender.
152 // However, the line descender has a negative value, hence the subtraction.
153 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
155 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
157 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
158 const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart );
159 bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( logicalModel->GetScript( selectionStart ) );
161 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
162 const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd );
163 bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( logicalModel->GetScript( selectionEndMinusOne ) );
165 // The number of quads of the selection box.
166 const unsigned int numberOfQuads = 1u + ( glyphEnd - glyphStart ) + ( ( numberOfLines > 1u ) ? 2u * numberOfLines : 0u );
167 decorator->ResizeHighlightQuads( numberOfQuads );
169 // Count the actual number of quads.
170 unsigned int actualNumberOfQuads = 0u;
173 // Traverse the glyphs.
174 for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
176 const GlyphInfo& glyph = *( glyphsBuffer + index );
177 const Vector2& position = *( positionsBuffer + index );
179 if( splitStartGlyph )
181 // 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.
183 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersStart );
184 const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart );
185 // Get the direction of the character.
186 CharacterDirection isCurrentRightToLeft = false;
187 if( nullptr != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
189 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart );
192 // The end point could be in the middle of the ligature.
193 // Calculate the number of characters selected.
194 const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex );
196 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + model->mScrollPosition.x + glyphAdvance * static_cast<float>( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex );
197 quad.y = selectionBoxInfo->lineOffset;
198 quad.z = quad.x + static_cast<float>( numberOfCharacters ) * glyphAdvance;
199 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
201 // Store the min and max 'x' for each line.
202 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
203 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
205 decorator->AddHighlight( actualNumberOfQuads, quad );
206 ++actualNumberOfQuads;
208 splitStartGlyph = false;
212 if( splitEndGlyph && ( index == glyphEnd ) )
214 // 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.
216 const float glyphAdvance = glyph.advance / static_cast<float>( numberOfCharactersEnd );
217 const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd );
218 // Get the direction of the character.
219 CharacterDirection isCurrentRightToLeft = false;
220 if( nullptr != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
222 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd );
225 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
227 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + model->mScrollPosition.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast<float>( numberOfCharacters ) ) : 0.f );
228 quad.y = selectionBoxInfo->lineOffset;
229 quad.z = quad.x + static_cast<float>( interGlyphIndex ) * glyphAdvance;
230 quad.w = quad.y + selectionBoxInfo->lineHeight;
232 // Store the min and max 'x' for each line.
233 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
234 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
236 decorator->AddHighlight( actualNumberOfQuads,
238 ++actualNumberOfQuads;
240 splitEndGlyph = false;
244 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + model->mScrollPosition.x;
245 quad.y = selectionBoxInfo->lineOffset;
246 quad.z = quad.x + glyph.advance;
247 quad.w = quad.y + selectionBoxInfo->lineHeight;
249 // Store the min and max 'x' for each line.
250 selectionBoxInfo->minX = std::min( selectionBoxInfo->minX, quad.x );
251 selectionBoxInfo->maxX = std::max( selectionBoxInfo->maxX, quad.z );
253 decorator->AddHighlight( actualNumberOfQuads,
255 ++actualNumberOfQuads;
257 // Whether to retrieve the next line.
258 if( index == lastGlyphOfLine )
261 if( lineIndex < firstLineIndex + numberOfLines )
263 // Retrieve the next line.
266 // Get the last glyph of the new line.
267 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
269 // Keep the offset and height of the current selection box.
270 const float currentLineOffset = selectionBoxInfo->lineOffset;
271 const float currentLineHeight = selectionBoxInfo->lineHeight;
273 // Get the selection box info for the next line.
276 selectionBoxInfo->minX = MAX_FLOAT;
277 selectionBoxInfo->maxX = MIN_FLOAT;
279 // Update the line's vertical offset.
280 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
282 // The line height is the addition of the line ascender and the line descender.
283 // However, the line descender has a negative value, hence the subtraction.
284 selectionBoxInfo->lineHeight = lineRun->ascender - lineRun->descender;
289 // Traverses all the lines and updates the min and max 'x' positions and the total height.
290 // The final width is calculated after 'boxifying' the selection.
291 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
292 endIt = selectionBoxLinesInfo.End();
296 const SelectionBoxInfo& info = *it;
298 // Update the size of the highlighted text.
299 highLightSize.height += info.lineHeight;
300 minHighlightX = std::min( minHighlightX, info.minX );
301 maxHighlightX = std::max( maxHighlightX, info.maxX );
304 // Add extra geometry to 'boxify' the selection.
306 if( 1u < numberOfLines )
308 // Boxify the first line.
309 lineRun = visualModel->mLines.Begin() + firstLineIndex;
310 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
312 bool boxifyBegin = ( LTR != lineRun->direction ) && ( LTR != startDirection );
313 bool boxifyEnd = ( LTR == lineRun->direction ) && ( LTR == startDirection );
318 quad.y = firstSelectionBoxLineInfo.lineOffset;
319 quad.z = firstSelectionBoxLineInfo.minX;
320 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
322 // Boxify at the beginning of the line.
323 decorator->AddHighlight( actualNumberOfQuads,
325 ++actualNumberOfQuads;
327 // Update the size of the highlighted text.
333 quad.x = firstSelectionBoxLineInfo.maxX;
334 quad.y = firstSelectionBoxLineInfo.lineOffset;
335 quad.z = visualModel->mControlSize.width;
336 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
338 // Boxify at the end of the line.
339 decorator->AddHighlight( actualNumberOfQuads,
341 ++actualNumberOfQuads;
343 // Update the size of the highlighted text.
344 maxHighlightX = visualModel->mControlSize.width;
347 // Boxify the central lines.
348 if( 2u < numberOfLines )
350 for( Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
351 endIt = selectionBoxLinesInfo.End() - 1u;
355 const SelectionBoxInfo& info = *it;
358 quad.y = info.lineOffset;
360 quad.w = info.lineOffset + info.lineHeight;
362 decorator->AddHighlight( actualNumberOfQuads,
364 ++actualNumberOfQuads;
367 quad.y = info.lineOffset;
368 quad.z = visualModel->mControlSize.width;
369 quad.w = info.lineOffset + info.lineHeight;
371 decorator->AddHighlight( actualNumberOfQuads,
373 ++actualNumberOfQuads;
376 // Update the size of the highlighted text.
378 maxHighlightX = visualModel->mControlSize.width;
381 // Boxify the last line.
382 lineRun = visualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
383 const SelectionBoxInfo& lastSelectionBoxLineInfo = *( selectionBoxLinesInfo.End() - 1u );
385 boxifyBegin = ( LTR == lineRun->direction ) && ( LTR == endDirection );
386 boxifyEnd = ( LTR != lineRun->direction ) && ( LTR != endDirection );
391 quad.y = lastSelectionBoxLineInfo.lineOffset;
392 quad.z = lastSelectionBoxLineInfo.minX;
393 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
395 // Boxify at the beginning of the line.
396 decorator->AddHighlight( actualNumberOfQuads,
398 ++actualNumberOfQuads;
400 // Update the size of the highlighted text.
406 quad.x = lastSelectionBoxLineInfo.maxX;
407 quad.y = lastSelectionBoxLineInfo.lineOffset;
408 quad.z = visualModel->mControlSize.width;
409 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
411 // Boxify at the end of the line.
412 decorator->AddHighlight(actualNumberOfQuads, quad);
413 ++actualNumberOfQuads;
415 // Update the size of the highlighted text.
416 maxHighlightX = visualModel->mControlSize.width;
420 // Set the actual number of quads.
421 decorator->ResizeHighlightQuads( actualNumberOfQuads );
423 // Sets the highlight's size and position. In decorator's coords.
424 // The highlight's height has been calculated above (before 'boxifying' the highlight).
425 highLightSize.width = maxHighlightX - minHighlightX;
427 highLightPosition.x = minHighlightX;
428 const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() );
429 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
431 decorator->SetHighLightBox( highLightPosition, highLightSize, static_cast<float>( model->GetOutlineWidth() ) );
433 if( !decorator->IsSmoothHandlePanEnabled() )
435 CursorInfo primaryCursorInfo;
436 impl.GetCursorPosition(eventData->mLeftSelectionPosition, primaryCursorInfo);
438 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + model->mScrollPosition;
440 decorator->SetPosition(LEFT_SELECTION_HANDLE,
442 primaryCursorInfo.lineOffset + model->mScrollPosition.y,
443 primaryCursorInfo.lineHeight );
445 CursorInfo secondaryCursorInfo;
446 impl.GetCursorPosition(eventData->mRightSelectionPosition, secondaryCursorInfo);
448 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + model->mScrollPosition;
450 decorator->SetPosition(RIGHT_SELECTION_HANDLE,
452 secondaryCursorInfo.lineOffset + model->mScrollPosition.y,
453 secondaryCursorInfo.lineHeight );
456 // Set the flag to update the decorator.
457 eventData->mDecoratorUpdated = true;
460 void SelectionHandleController::Reposition(Controller::Impl& impl, float visualX, float visualY, Controller::NoTextTap::Action action)
462 EventData*& eventData = impl.mEventData;
463 if(nullptr == eventData)
465 // Nothing to do if there is no text input.
469 if(impl.IsShowingPlaceholderText())
471 // Nothing to do if there is the place-holder text.
475 ModelPtr& model = impl.mModel;
476 VisualModelPtr& visualModel = model->mVisualModel;
477 const Length numberOfGlyphs = visualModel->mGlyphs.Count();
478 const Length numberOfLines = visualModel->mLines.Count();
479 if( ( 0 == numberOfGlyphs ) ||
480 ( 0 == numberOfLines ) )
482 // Nothing to do if there is no text.
486 // Find which word was selected
487 CharacterIndex selectionStart( 0 );
488 CharacterIndex selectionEnd( 0 );
489 CharacterIndex noTextHitIndex( 0 );
490 const bool characterHit = FindSelectionIndices( visualModel,
491 model->mLogicalModel,
498 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", &impl, selectionStart, selectionEnd );
500 if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) )
502 impl.ChangeState( EventData::SELECTING );
504 eventData->mLeftSelectionPosition = selectionStart;
505 eventData->mRightSelectionPosition = selectionEnd;
507 eventData->mUpdateLeftSelectionPosition = true;
508 eventData->mUpdateRightSelectionPosition = true;
509 eventData->mUpdateHighlightBox = true;
511 // It may happen an InputMethodContext commit event arrives before the selection event
512 // if the InputMethodContext is in pre-edit state. The commit event will set the
513 // eventData->mUpdateCursorPosition flag to true. If it's not set back
514 // to false, the highlight box won't be updated.
515 eventData->mUpdateCursorPosition = false;
517 eventData->mScrollAfterUpdatePosition = ( eventData->mLeftSelectionPosition != eventData->mRightSelectionPosition );
519 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
520 eventData->mPrimaryCursorPosition = std::max( eventData->mLeftSelectionPosition, eventData->mRightSelectionPosition );
522 else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action )
524 // Nothing to select. i.e. a white space, out of bounds
525 impl.ChangeState( EventData::EDITING_WITH_POPUP );
527 eventData->mPrimaryCursorPosition = noTextHitIndex;
529 eventData->mUpdateCursorPosition = true;
530 eventData->mUpdateGrabHandlePosition = true;
531 eventData->mScrollAfterUpdatePosition = true;
532 eventData->mUpdateInputStyle = true;
534 else if( Controller::NoTextTap::NO_ACTION == action )
536 // Nothing to select. i.e. a white space, out of bounds
537 eventData->mPrimaryCursorPosition = noTextHitIndex;
539 eventData->mUpdateCursorPosition = true;
540 eventData->mUpdateGrabHandlePosition = true;
541 eventData->mScrollAfterUpdatePosition = true;
542 eventData->mUpdateInputStyle = true;
546 void SelectionHandleController::Update(Controller::Impl& impl, HandleType handleType, const CursorInfo& cursorInfo)
548 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
549 ( RIGHT_SELECTION_HANDLE != handleType ) )
554 ModelPtr& model = impl.mModel;
555 const Vector2 cursorPosition = cursorInfo.primaryPosition + model->mScrollPosition;
557 // Sets the handle's position.
558 EventData*& eventData = impl.mEventData;
559 eventData->mDecorator->SetPosition(handleType,
561 cursorInfo.lineOffset + model->mScrollPosition.y,
562 cursorInfo.lineHeight );
564 // If selection handle at start of the text and other at end of the text then all text is selected.
565 const CharacterIndex startOfSelection = std::min( eventData->mLeftSelectionPosition, eventData->mRightSelectionPosition );
566 const CharacterIndex endOfSelection = std::max ( eventData->mLeftSelectionPosition, eventData->mRightSelectionPosition );
567 eventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == model->mLogicalModel->mText.Count() );
572 } // namespace Toolkit