2 * Copyright (c) 2021 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>
33 * @brief Struct used to calculate the selection box.
35 struct SelectionBoxInfo
43 #if defined(DEBUG_ENABLED)
44 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
47 const float MAX_FLOAT = std::numeric_limits<float>::max();
48 const float MIN_FLOAT = std::numeric_limits<float>::min();
49 const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction
59 void SelectionHandleController::Reposition(Controller::Impl& impl)
61 EventData*& eventData = impl.mEventData;
63 CharacterIndex selectionStart = eventData->mLeftSelectionPosition;
64 CharacterIndex selectionEnd = eventData->mRightSelectionPosition;
66 DecoratorPtr& decorator = eventData->mDecorator;
68 if(selectionStart == selectionEnd)
70 // Nothing to select if handles are in the same place.
71 // So, deactive Highlight box.
72 decorator->SetHighlightActive(false);
76 decorator->ClearHighlights();
78 ModelPtr& model = impl.mModel;
79 VisualModelPtr& visualModel = model->mVisualModel;
80 LogicalModelPtr& logicalModel = model->mLogicalModel;
82 const GlyphIndex* const charactersToGlyphBuffer = visualModel->mCharactersToGlyph.Begin();
83 const Length* const glyphsPerCharacterBuffer = visualModel->mGlyphsPerCharacter.Begin();
84 const GlyphInfo* const glyphsBuffer = visualModel->mGlyphs.Begin();
85 const Vector2* const positionsBuffer = visualModel->mGlyphPositions.Begin();
86 const Length* const charactersPerGlyphBuffer = visualModel->mCharactersPerGlyph.Begin();
87 const CharacterIndex* const glyphToCharacterBuffer = visualModel->mGlyphsToCharacters.Begin();
88 const CharacterDirection* const modelCharacterDirectionsBuffer = (0u != logicalModel->mCharacterDirections.Count()) ? logicalModel->mCharacterDirections.Begin() : NULL;
90 const bool isLastCharacter = selectionEnd >= logicalModel->mText.Count();
91 const CharacterDirection startDirection = ((NULL == modelCharacterDirectionsBuffer) ? false : *(modelCharacterDirectionsBuffer + selectionStart));
92 const CharacterDirection endDirection = ((NULL == modelCharacterDirectionsBuffer) ? false : *(modelCharacterDirectionsBuffer + (selectionEnd - (isLastCharacter ? 1u : 0u))));
94 // Swap the indices if the start is greater than the end.
95 const bool indicesSwapped = selectionStart > selectionEnd;
97 // Tell the decorator to flip the selection handles if needed.
98 decorator->SetSelectionHandleFlipState(indicesSwapped, startDirection, endDirection);
102 std::swap(selectionStart, selectionEnd);
105 // Get the indices to the first and last selected glyphs.
106 const CharacterIndex selectionEndMinusOne = selectionEnd - 1u;
107 const GlyphIndex glyphStart = *(charactersToGlyphBuffer + selectionStart);
108 const Length numberOfGlyphs = *(glyphsPerCharacterBuffer + selectionEndMinusOne);
109 const GlyphIndex glyphEnd = *(charactersToGlyphBuffer + selectionEndMinusOne) + ((numberOfGlyphs > 0) ? numberOfGlyphs - 1u : 0u);
111 // Get the lines where the glyphs are laid-out.
112 const LineRun* lineRun = visualModel->mLines.Begin();
114 LineIndex lineIndex = 0u;
115 Length numberOfLines = 0u;
116 visualModel->GetNumberOfLines(glyphStart,
117 1u + glyphEnd - glyphStart,
120 const LineIndex firstLineIndex = lineIndex;
122 // Create the structure to store some selection box info.
123 Vector<SelectionBoxInfo> selectionBoxLinesInfo;
124 selectionBoxLinesInfo.Resize(numberOfLines);
126 SelectionBoxInfo* selectionBoxInfo = selectionBoxLinesInfo.Begin();
127 selectionBoxInfo->minX = MAX_FLOAT;
128 selectionBoxInfo->maxX = MIN_FLOAT;
130 // Keep the min and max 'x' position to calculate the size and position of the highlighed text.
131 float minHighlightX = std::numeric_limits<float>::max();
132 float maxHighlightX = std::numeric_limits<float>::min();
134 Vector2 highLightPosition; // The highlight position in decorator's coords.
136 // Retrieve the first line and get the line's vertical offset, the line's height and the index to the last glyph.
138 // The line's vertical offset of all the lines before the line where the first glyph is laid-out.
139 selectionBoxInfo->lineOffset = CalculateLineOffset(visualModel->mLines,
142 // Transform to decorator's (control) coords.
143 selectionBoxInfo->lineOffset += model->mScrollPosition.y;
145 lineRun += firstLineIndex;
147 selectionBoxInfo->lineHeight = GetLineHeight(*lineRun);
149 GlyphIndex lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
151 // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
152 const Length numberOfCharactersStart = *(charactersPerGlyphBuffer + glyphStart);
153 bool splitStartGlyph = (numberOfCharactersStart > 1u) && HasLigatureMustBreak(logicalModel->GetScript(selectionStart));
155 // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ﻻ, etc which needs special code.
156 const Length numberOfCharactersEnd = *(charactersPerGlyphBuffer + glyphEnd);
157 bool splitEndGlyph = (glyphStart != glyphEnd) && (numberOfCharactersEnd > 1u) && HasLigatureMustBreak(logicalModel->GetScript(selectionEndMinusOne));
159 // The number of quads of the selection box.
160 const unsigned int numberOfQuads = 1u + (glyphEnd - glyphStart) + ((numberOfLines > 1u) ? 2u * numberOfLines : 0u);
161 decorator->ResizeHighlightQuads(numberOfQuads);
163 // Count the actual number of quads.
164 unsigned int actualNumberOfQuads = 0u;
167 // Traverse the glyphs.
168 for(GlyphIndex index = glyphStart; index <= glyphEnd; ++index)
170 const GlyphInfo& glyph = *(glyphsBuffer + index);
171 const Vector2& position = *(positionsBuffer + index);
175 // 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.
177 const float glyphAdvance = glyph.advance / static_cast<float>(numberOfCharactersStart);
178 const CharacterIndex interGlyphIndex = selectionStart - *(glyphToCharacterBuffer + glyphStart);
179 // Get the direction of the character.
180 CharacterDirection isCurrentRightToLeft = false;
181 if(nullptr != modelCharacterDirectionsBuffer) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
183 isCurrentRightToLeft = *(modelCharacterDirectionsBuffer + selectionStart);
186 // The end point could be in the middle of the ligature.
187 // Calculate the number of characters selected.
188 const Length numberOfCharacters = (glyphStart == glyphEnd) ? (selectionEnd - selectionStart) : (numberOfCharactersStart - interGlyphIndex);
190 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + model->mScrollPosition.x + glyphAdvance * static_cast<float>(isCurrentRightToLeft ? (numberOfCharactersStart - interGlyphIndex - numberOfCharacters) : interGlyphIndex);
191 quad.y = selectionBoxInfo->lineOffset;
192 quad.z = quad.x + static_cast<float>(numberOfCharacters) * glyphAdvance;
193 quad.w = selectionBoxInfo->lineOffset + selectionBoxInfo->lineHeight;
195 // Store the min and max 'x' for each line.
196 selectionBoxInfo->minX = std::min(selectionBoxInfo->minX, quad.x);
197 selectionBoxInfo->maxX = std::max(selectionBoxInfo->maxX, quad.z);
199 decorator->AddHighlight(actualNumberOfQuads, quad);
200 ++actualNumberOfQuads;
202 splitStartGlyph = false;
206 if(splitEndGlyph && (index == glyphEnd))
208 // 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.
210 const float glyphAdvance = glyph.advance / static_cast<float>(numberOfCharactersEnd);
211 const CharacterIndex interGlyphIndex = selectionEnd - *(glyphToCharacterBuffer + glyphEnd);
212 // Get the direction of the character.
213 CharacterDirection isCurrentRightToLeft = false;
214 if(nullptr != modelCharacterDirectionsBuffer) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
216 isCurrentRightToLeft = *(modelCharacterDirectionsBuffer + selectionEnd);
219 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex;
221 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + model->mScrollPosition.x + (isCurrentRightToLeft ? (glyphAdvance * static_cast<float>(numberOfCharacters)) : 0.f);
222 quad.y = selectionBoxInfo->lineOffset;
223 quad.z = quad.x + static_cast<float>(interGlyphIndex) * glyphAdvance;
224 quad.w = quad.y + selectionBoxInfo->lineHeight;
226 // Store the min and max 'x' for each line.
227 selectionBoxInfo->minX = std::min(selectionBoxInfo->minX, quad.x);
228 selectionBoxInfo->maxX = std::max(selectionBoxInfo->maxX, quad.z);
230 decorator->AddHighlight(actualNumberOfQuads,
232 ++actualNumberOfQuads;
234 splitEndGlyph = false;
238 quad.x = lineRun->alignmentOffset + position.x - glyph.xBearing + model->mScrollPosition.x;
239 quad.y = selectionBoxInfo->lineOffset;
240 quad.z = quad.x + glyph.advance;
241 quad.w = quad.y + selectionBoxInfo->lineHeight;
243 // Store the min and max 'x' for each line.
244 selectionBoxInfo->minX = std::min(selectionBoxInfo->minX, quad.x);
245 selectionBoxInfo->maxX = std::max(selectionBoxInfo->maxX, quad.z);
247 decorator->AddHighlight(actualNumberOfQuads,
249 ++actualNumberOfQuads;
251 // Whether to retrieve the next line.
252 if(index == lastGlyphOfLine)
255 if(lineIndex < firstLineIndex + numberOfLines)
257 // Retrieve the next line.
260 // Get the last glyph of the new line.
261 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
263 // Keep the offset and height of the current selection box.
264 const float currentLineOffset = selectionBoxInfo->lineOffset;
265 const float currentLineHeight = selectionBoxInfo->lineHeight;
267 // Get the selection box info for the next line.
270 selectionBoxInfo->minX = MAX_FLOAT;
271 selectionBoxInfo->maxX = MIN_FLOAT;
273 // Update the line's vertical offset.
274 selectionBoxInfo->lineOffset = currentLineOffset + currentLineHeight;
276 selectionBoxInfo->lineHeight = GetLineHeight(*lineRun);
281 // Traverses all the lines and updates the min and max 'x' positions and the total height.
282 // The final width is calculated after 'boxifying' the selection.
283 for(Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin(),
284 endIt = selectionBoxLinesInfo.End();
288 const SelectionBoxInfo& info = *it;
290 // Update the size of the highlighted text.
291 highLightSize.height += info.lineHeight;
292 minHighlightX = std::min(minHighlightX, info.minX);
293 maxHighlightX = std::max(maxHighlightX, info.maxX);
296 // Add extra geometry to 'boxify' the selection.
298 if(1u < numberOfLines)
300 // Boxify the first line.
301 lineRun = visualModel->mLines.Begin() + firstLineIndex;
302 const SelectionBoxInfo& firstSelectionBoxLineInfo = *(selectionBoxLinesInfo.Begin());
304 bool boxifyBegin = (LTR != lineRun->direction) && (LTR != startDirection);
305 bool boxifyEnd = (LTR == lineRun->direction) && (LTR == startDirection);
310 quad.y = firstSelectionBoxLineInfo.lineOffset;
311 quad.z = firstSelectionBoxLineInfo.minX;
312 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
314 // Boxify at the beginning of the line.
315 decorator->AddHighlight(actualNumberOfQuads,
317 ++actualNumberOfQuads;
319 // Update the size of the highlighted text.
325 quad.x = firstSelectionBoxLineInfo.maxX;
326 quad.y = firstSelectionBoxLineInfo.lineOffset;
327 quad.z = visualModel->mControlSize.width;
328 quad.w = firstSelectionBoxLineInfo.lineOffset + firstSelectionBoxLineInfo.lineHeight;
330 // Boxify at the end of the line.
331 decorator->AddHighlight(actualNumberOfQuads,
333 ++actualNumberOfQuads;
335 // Update the size of the highlighted text.
336 maxHighlightX = visualModel->mControlSize.width;
339 // Boxify the central lines.
340 if(2u < numberOfLines)
342 for(Vector<SelectionBoxInfo>::ConstIterator it = selectionBoxLinesInfo.Begin() + 1u,
343 endIt = selectionBoxLinesInfo.End() - 1u;
347 const SelectionBoxInfo& info = *it;
350 quad.y = info.lineOffset;
352 quad.w = info.lineOffset + info.lineHeight;
354 decorator->AddHighlight(actualNumberOfQuads,
356 ++actualNumberOfQuads;
359 quad.y = info.lineOffset;
360 quad.z = visualModel->mControlSize.width;
361 quad.w = info.lineOffset + info.lineHeight;
363 decorator->AddHighlight(actualNumberOfQuads,
365 ++actualNumberOfQuads;
368 // Update the size of the highlighted text.
370 maxHighlightX = visualModel->mControlSize.width;
373 // Boxify the last line.
374 lineRun = visualModel->mLines.Begin() + firstLineIndex + numberOfLines - 1u;
375 const SelectionBoxInfo& lastSelectionBoxLineInfo = *(selectionBoxLinesInfo.End() - 1u);
377 boxifyBegin = (LTR == lineRun->direction) && (LTR == endDirection);
378 boxifyEnd = (LTR != lineRun->direction) && (LTR != endDirection);
383 quad.y = lastSelectionBoxLineInfo.lineOffset;
384 quad.z = lastSelectionBoxLineInfo.minX;
385 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
387 // Boxify at the beginning of the line.
388 decorator->AddHighlight(actualNumberOfQuads,
390 ++actualNumberOfQuads;
392 // Update the size of the highlighted text.
398 quad.x = lastSelectionBoxLineInfo.maxX;
399 quad.y = lastSelectionBoxLineInfo.lineOffset;
400 quad.z = visualModel->mControlSize.width;
401 quad.w = lastSelectionBoxLineInfo.lineOffset + lastSelectionBoxLineInfo.lineHeight;
403 // Boxify at the end of the line.
404 decorator->AddHighlight(actualNumberOfQuads, quad);
405 ++actualNumberOfQuads;
407 // Update the size of the highlighted text.
408 maxHighlightX = visualModel->mControlSize.width;
412 // Set the actual number of quads.
413 decorator->ResizeHighlightQuads(actualNumberOfQuads);
415 // Sets the highlight's size and position. In decorator's coords.
416 // The highlight's height has been calculated above (before 'boxifying' the highlight).
417 highLightSize.width = maxHighlightX - minHighlightX;
419 highLightPosition.x = minHighlightX;
420 const SelectionBoxInfo& firstSelectionBoxLineInfo = *(selectionBoxLinesInfo.Begin());
421 highLightPosition.y = firstSelectionBoxLineInfo.lineOffset;
423 decorator->SetHighLightBox(highLightPosition, highLightSize, static_cast<float>(model->GetOutlineWidth()));
425 if(!decorator->IsSmoothHandlePanEnabled())
427 CursorInfo primaryCursorInfo;
428 impl.GetCursorPosition(eventData->mLeftSelectionPosition, primaryCursorInfo);
430 const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + model->mScrollPosition;
432 decorator->SetPosition(LEFT_SELECTION_HANDLE,
434 primaryCursorInfo.lineOffset + model->mScrollPosition.y,
435 primaryCursorInfo.lineHeight);
437 CursorInfo secondaryCursorInfo;
438 impl.GetCursorPosition(eventData->mRightSelectionPosition, secondaryCursorInfo);
440 const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + model->mScrollPosition;
442 decorator->SetPosition(RIGHT_SELECTION_HANDLE,
444 secondaryCursorInfo.lineOffset + model->mScrollPosition.y,
445 secondaryCursorInfo.lineHeight);
448 // Set the flag to update the decorator.
449 eventData->mDecoratorUpdated = true;
452 void SelectionHandleController::Reposition(Controller::Impl& impl, float visualX, float visualY, Controller::NoTextTap::Action action)
454 EventData*& eventData = impl.mEventData;
455 if(nullptr == eventData)
457 // Nothing to do if there is no text input.
461 if(impl.IsShowingPlaceholderText())
463 // Nothing to do if there is the place-holder text.
467 ModelPtr& model = impl.mModel;
468 VisualModelPtr& visualModel = model->mVisualModel;
469 const Length numberOfGlyphs = visualModel->mGlyphs.Count();
470 const Length numberOfLines = visualModel->mLines.Count();
471 if((0 == numberOfGlyphs) ||
472 (0 == numberOfLines))
474 // Nothing to do if there is no text.
478 // Find which word was selected
479 CharacterIndex selectionStart(0);
480 CharacterIndex selectionEnd(0);
481 CharacterIndex noTextHitIndex(0);
482 const bool characterHit = FindSelectionIndices(visualModel,
483 model->mLogicalModel,
490 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", &impl, selectionStart, selectionEnd);
492 if(characterHit || (Controller::NoTextTap::HIGHLIGHT == action))
494 uint32_t oldStart = eventData->mLeftSelectionPosition;
495 uint32_t oldEnd = eventData->mRightSelectionPosition;
497 impl.ChangeState(EventData::SELECTING);
499 eventData->mLeftSelectionPosition = selectionStart;
500 eventData->mRightSelectionPosition = selectionEnd;
502 eventData->mUpdateLeftSelectionPosition = true;
503 eventData->mUpdateRightSelectionPosition = true;
504 eventData->mUpdateHighlightBox = true;
506 // It may happen an InputMethodContext commit event arrives before the selection event
507 // if the InputMethodContext is in pre-edit state. The commit event will set the
508 // eventData->mUpdateCursorPosition flag to true. If it's not set back
509 // to false, the highlight box won't be updated.
510 eventData->mUpdateCursorPosition = false;
512 eventData->mScrollAfterUpdatePosition = (eventData->mLeftSelectionPosition != eventData->mRightSelectionPosition);
514 // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection
515 eventData->mPrimaryCursorPosition = std::max(eventData->mLeftSelectionPosition, eventData->mRightSelectionPosition);
517 if(impl.mSelectableControlInterface != nullptr)
519 impl.mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, 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