123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include "UiTextComponentOffsetsSelector.h"
- #include "StringUtfUtils.h"
- void UiTextComponentOffsetsSelector::ParseBatchLine(const UiTextComponent::DrawBatchLine& batchLine, float& curLineWidth)
- {
- // Knowing the length of the line helps with alignment calculations
- lineOffsetsStack.top()->batchLineLength = batchLine.lineSize.GetX();
- // The "current line index" resets to zero with each new line. This
- // index allows us to index relative to the current line of text
- // we're iterating on.
- int curLineIndexIter = 0;
- // Keep track of where m_firstIndex occurs relative to the current line.
- // This is needed when m_firstIndex and m_lastIndex occur on the same line
- // to obtain the selection range for that line.
- int firstIndexLineIndex = 0;
- // For input text, we could safely assume one DrawBatch per line,
- // since we don't support marked-up input (at least for now). But
- // it's easy enough to iterate through the list anyways.
- for (const UiTextComponent::DrawBatch& drawBatch : batchLine.drawBatchList)
- {
- // Iterate character by character over DrawBatch string contents,
- // looking for m_firstIndex and m_lastIndex.
- Utf8::Unchecked::octet_iterator pChar(drawBatch.text.data());
- while (uint32_t ch = *pChar)
- {
- ++pChar;
- if (m_indexIter == m_firstIndex)
- {
- firstIndexFound = true;
- firstIndexLineIndex = curLineIndexIter;
- // Get the width of the string of characters prior to the
- // selection string. This will be used to offset the
- // cursor position from the left of the start of the line.
- AZStd::string unselectedPrecedingString(drawBatch.text.substr(0, firstIndexLineIndex));
- lineOffsetsStack.top()->left.SetX(curLineWidth + drawBatch.font->GetTextSize(unselectedPrecedingString.c_str(), false, m_fontContext).x);
- if (m_firstIndex == m_lastIndex)
- {
- lastIndexFound = true;
- lineOffsetsStack.top()->right = AZ::Vector2::CreateZero();
- break;
- }
- }
- else if (m_indexIter == m_lastIndex)
- {
- lastIndexFound = true;
- // The number of chars selected (selection length) for this
- // line depends on whether the selection is split across multiple lines.
- const int selectionLength = firstAndLastIndexOccurOnDifferentLines ? curLineIndexIter : curLineIndexIter - firstIndexLineIndex;
- AZStd::string selectionString(drawBatch.text.substr(firstIndexLineIndex, selectionLength));
- Vec2 rightSize = drawBatch.font->GetTextSize(selectionString.c_str(), true, m_fontContext);
- lineOffsetsStack.top()->right.SetX(rightSize.x);
- m_numCharsSelected += LyShine::GetUtf8StringLength(selectionString);
- break;
- }
- // Iterate both curLineIndexIter (the index relative to this
- // line) and m_indexIter (the 'global' index for iterating across
- // the entire rendered string).
- curLineIndexIter += LyShine::GetMultiByteCharSize(ch);
- ++m_indexIter;
- }
- // We're done iterating through the string contents of this DrawBatch
- // for this line and we still haven't found m_firstIndex. In this case,
- // we can add the entire width of the DrawBatch contents to the current
- // line width.
- if (!firstIndexFound)
- {
- curLineWidth += drawBatch.font->GetTextSize(drawBatch.text.c_str(), false, m_fontContext).x;
- }
- // If m_firstIndex has been found, but we haven't found m_lastIndex, we
- // calculate curLineWidth relative to firstIndexLineIndex (the m_firstIndex
- // position relative to the current line). Note that firstIndexLineIndex
- // is reset to zero with each line we iterate on. This allows us to
- // select the substring for the current line whether m_firstIndex occurs
- // on the same line or not.
- else if (!lastIndexFound)
- {
- int substrLength = static_cast<int>(drawBatch.text.length() - firstIndexLineIndex);
- AZStd::string curSubstring(drawBatch.text.substr(firstIndexLineIndex, substrLength));
- curLineWidth += drawBatch.font->GetTextSize(curSubstring.c_str(), false, m_fontContext).x;
- lineOffsetsStack.top()->right.SetX(AZStd::GetMax<float>(lineOffsetsStack.top()->right.GetX(), curLineWidth));
- m_numCharsSelected += LyShine::GetUtf8StringLength(curSubstring);
- }
- }
- }
- void UiTextComponentOffsetsSelector::HandleTopAndMiddleOffsets()
- {
- const bool topOffsetNeedsPopping = 3 == lineOffsetsStack.size();
- const bool middleOffsetNeedsPopping = m_lineCounter + 1 == m_lastIndexLineNumber;
- if (topOffsetNeedsPopping)
- {
- const float curHeightOffset = lineOffsetsStack.top()->left.GetY() + lineOffsetsStack.top()->right.GetY();
- lineOffsetsStack.pop();
- // We take the max here in case the top offset occurs on
- // the first line (in which case the height offset would be zero).
- // This either pushes the cursor to the following line
- // (m_fontSize) or following lines if an offset is applied (curHeightOffset).
- lineOffsetsStack.top()->left.SetY(AZStd::GetMax<float>(curHeightOffset, m_fontSize));
- // Always reset right (relative) y-offset when a new left
- // ("absolute") y-offset is assigned.
- lineOffsetsStack.top()->right.SetY(0.0f);
- }
- else if (middleOffsetNeedsPopping)
- {
- const float curHeightOffset = lineOffsetsStack.top()->left.GetY() + lineOffsetsStack.top()->right.GetY();
- lineOffsetsStack.pop();
- // We need to substract m_fontSize here to "prime" for the
- // fact that we'll be adding it back in, below.
- lineOffsetsStack.top()->left.SetY(lineOffsetsStack.top()->left.GetY() + (curHeightOffset - m_fontSize));
- // Always reset right (relative) y-offset when a new left
- // ("absolute") y-offset is assigned.
- lineOffsetsStack.top()->right.SetY(0.0f);
- }
- }
- void UiTextComponentOffsetsSelector::IncrementYOffsets()
- {
- // We increment the left (absolute) y-offset only when we are NOT
- // iterating through a "middle" section. Once we hit a middle
- // section, we want to lock/freeze the left (absolute) y-offset
- // position and only increment the right (relative) y-offset
- // position. This allows the rendered rect to span the entirety
- // of the selection.
- const bool iteratingOnMiddleSection = 2 == lineOffsetsStack.size() && m_lineCounter < m_numLines;
- if (!iteratingOnMiddleSection)
- {
- lineOffsetsStack.top()->left.SetY(lineOffsetsStack.top()->left.GetY() + m_fontSize);
- // Always reset right (relative) y-offset when a new left
- // ("absolute") y-offset is assigned.
- lineOffsetsStack.top()->right.SetY(0.0f);
- }
- lineOffsetsStack.top()->right.SetY(lineOffsetsStack.top()->right.GetY() + m_fontSize);
- }
- void UiTextComponentOffsetsSelector::CalculateOffsets(UiTextComponent::LineOffsets& top, UiTextComponent::LineOffsets& middle, UiTextComponent::LineOffsets& bottom)
- {
- lineOffsetsStack.push(&bottom);
- lineOffsetsStack.push(&middle);
- lineOffsetsStack.push(&top);
- // Iterate over each rendered line of text, operating on the top of the
- // line offsets stack. The stack is popped as each section is completed.
- // Since the bottom section is the last section, there's no need to pop
- // it off the stack.
- for (const UiTextComponent::DrawBatchLine& batchLine : m_drawBatchLines.batchLines)
- {
- m_lineCounter++;
- float curLineWidth = 0.0f;
- // X offset gets reset for every new line we iterate
- lineOffsetsStack.top()->left.SetX(0.0f);
- ParseBatchLine(batchLine, curLineWidth);
- // Handle the special case where the index is at the end of the string
- // (1 beyond the string index, technically) and there is no selection.
- // For this case we want to display the cursor at the end of the string,
- // so we assign the curLineWidth to the left X offset.
- const bool cursorAtEndOfString = lineOffsetsStack.top()->left.GetX() == 0.0f;
- if (cursorAtEndOfString && m_firstIndex == m_lastIndex)
- {
- lineOffsetsStack.top()->left.SetX(curLineWidth);
- }
- const bool noSelection = m_firstIndex == m_lastIndex;
- const bool onLineHint = m_lineCounter == m_lineNumHint;
- const bool onIndex = m_indexIter == m_firstIndex;
- const bool shouldPlaceIndexOnThisLine = noSelection && onLineHint && onIndex;
- if (shouldPlaceIndexOnThisLine)
- {
- firstIndexFound = lastIndexFound = true;
- }
- // If we still haven't found m_firstIndex, we can skip additional
- // early-out, stack-popping logic, etc.
- if (firstIndexFound)
- {
- // It's possible to have all the characters selected but never found
- // m_lastIndex because m_lastIndex could be 1-beyond the string array.
- // For example, if all characters are selected, then m_lastIndex is
- // actually 1-beyond the array extents, so we account for that here.
- const bool allCharsSelected = m_numCharsSelected > 0 && m_numCharsSelected == m_lastIndex - m_firstIndex;
- if (lastIndexFound || allCharsSelected)
- {
- // Nothing left to do
- break;
- }
- else
- {
- HandleTopAndMiddleOffsets();
- firstAndLastIndexOccurOnDifferentLines = true;
- }
- }
- // When cursor is at end of text, last and first index booleans technically
- // aren't found because the cursor is one past the end of the string
- // array, so execution comes to this point.
- const bool cursorAtEndOfText = cursorAtEndOfString && m_lineCounter == m_numLines;
- if (!cursorAtEndOfText)
- {
- IncrementYOffsets();
- }
- }
- }
|