From 898bb4cb04323871c9c28ce0aaf6ccb78bd0099d Mon Sep 17 00:00:00 2001 From: Agnelo Vaz Date: Fri, 30 May 2014 14:15:25 +0100 Subject: [PATCH] TextInput Adding Highlight Class to be used by Decorator [problem] Highlight class previously stubbed. [cause] Part of incremental refactor [solution] Implement Hightlight class, currently built but not used in existing TextInput. Will be used in future patch. Change-Id: I6ab0e63fcfdd4f3ad667ce5a83fb708d751fd476 --- .../text-input/text-input-text-highlight-impl.cpp | 384 +++++++++++++++++++++ .../text-input/text-input-text-highlight-impl.h | 17 +- base/dali-toolkit/internal/file.list | 1 + 3 files changed, 393 insertions(+), 9 deletions(-) create mode 100644 base/dali-toolkit/internal/controls/text-input/text-input-text-highlight-impl.cpp diff --git a/base/dali-toolkit/internal/controls/text-input/text-input-text-highlight-impl.cpp b/base/dali-toolkit/internal/controls/text-input/text-input-text-highlight-impl.cpp new file mode 100644 index 0000000..d42abcd --- /dev/null +++ b/base/dali-toolkit/internal/controls/text-input/text-input-text-highlight-impl.cpp @@ -0,0 +1,384 @@ +// +// Copyright (c) 2014 Samsung Electronics Co., Ltd. +// +// Licensed under the Flora License, Version 1.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://floralicense.org/license/ +// +// 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 + +#include +#include +#include +#include + +using namespace Dali; +using namespace std; + +namespace +{ + /** + * Selection state enumeration (FSM) + */ + enum SelectionState + { + SelectionNone, ///< Currently not encountered selected section. + SelectionStarted, ///< Encountered selected section + SelectionFinished ///< Finished selected section + }; + + const Vector4 LIGHTBLUE( 10.0f/255.0f, 140.0f/255.0f, 210.0f/255.0f, 1.0f ); // todo make this a setting + + const float CHARACTER_THRESHOLD( 2.5f ); // todo check if unified method to do this in Text +} + +namespace Dali +{ + +namespace Toolkit +{ + +namespace Internal +{ + +// Default constructor +TextHighlight::TextHighlight( TextViewCharacterPositioning& textViewCharacterPositioning ) : + mTextViewCharacterPositioning( textViewCharacterPositioning ) +{ +} + +TextHighlight::~TextHighlight() +{ +} + +void TextHighlight::GetVisualTextSelection(std::vector& selectedVisualText, std::size_t startSelection, std::size_t endSelection, + Toolkit::TextView::TextLayoutInfo& textLayoutInfo ) +{ + std::vector::iterator it = textLayoutInfo.mCharacterLogicalToVisualMap.begin(); + std::vector::iterator startSelectionIt = textLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::min(startSelection, endSelection); + std::vector::iterator endSelectionIt = textLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::max(startSelection, endSelection); + std::vector::iterator end = textLayoutInfo.mCharacterLogicalToVisualMap.end(); + + selectedVisualText.resize( mTextViewCharacterPositioning.GetNumberOfCharactersInText() ); + + // Deselect text prior to startSelectionIt + for(;it!=startSelectionIt;++it) + { + selectedVisualText[*it] = false; + } + + // Select text from startSelectionIt -> endSelectionIt + for(;it!=endSelectionIt;++it) + { + selectedVisualText[*it] = true; + } + + // Deselect text after endSelection + for(;it!=end;++it) + { + selectedVisualText[*it] = false; + } + + selectedVisualText.resize( mTextViewCharacterPositioning.GetNumberOfCharactersInText(), false ); +} + +// Calculate the dimensions of the quads they will make the highlight mesh +TextHighlight::HighlightInfo TextHighlight::CalculateHighlightInfo( std::size_t handlePositionStart, std::size_t handlePositionEnd, Toolkit::TextView::TextLayoutInfo& textLayoutInfo ) +{ + // At the moment there is no public API to modify the block alignment option. + const bool blockAlignEnabled = true; + + TextHighlight::HighlightInfo newHighlightInfo; + //newHighlightInfo.mQuadList.clear(); // clear last quad information. + + if ( !mTextViewCharacterPositioning.IsTextEmpty() && !textLayoutInfo.mCharacterLogicalToVisualMap.empty() ) + { + // Get vector of flags representing characters that are selected (true) vs unselected (false). + std::vector selectedVisualText; + GetVisualTextSelection(selectedVisualText, handlePositionStart, handlePositionEnd, textLayoutInfo ); + std::vector::iterator selectedIt(selectedVisualText.begin()); + std::vector::iterator selectedEndIt(selectedVisualText.end()); + + SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text. + float rowLeft = 0.0f; + float rowRight = 0.0f; + // Keep track of the TextView's min/max extents. Should be able to query this from TextView. + float maxRowLeft = std::numeric_limits::max(); + float maxRowRight = 0.0f; + + Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = textLayoutInfo.mCharacterLayoutInfoTable.begin(); + Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = textLayoutInfo.mCharacterLayoutInfoTable.end(); + + Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it; + + // Scan through entire text. + while(it != end) + { + // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection. + + Toolkit::TextView::CharacterLayoutInfo& charInfo(*it); + bool charSelected( false ); + if( selectedIt != selectedEndIt ) + { + charSelected = *selectedIt++; + } + + if(selectionState == SelectionNone) + { + if(charSelected) + { + selectionState = SelectionStarted; + rowLeft = charInfo.mPosition.x - textLayoutInfo.mScrollOffset.x; + rowRight = rowLeft + charInfo.mSize.width; + } + } + else if(selectionState == SelectionStarted) + { + // break selection on: + // 1. new line causing selection break. (\n or wordwrap) + // 2. character not selected. + if(charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD || + !charSelected) + { + // finished selection. + // TODO: TextView should have a table of visual rows, and each character a reference to the row + // that it resides on. That way this enumeration is not necessary. + Vector2 min, max; + if(lastIt->mIsNewLineChar) + { + // If the last character is a new line, then to get the row rect, we need to scan from the character before the new line. + lastIt = std::max( textLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 ); + } + const Size rowSize( mTextViewCharacterPositioning.GetRowRectFromCharacterPosition( lastIt - textLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) ); + maxRowLeft = std::min(maxRowLeft, min.x); + maxRowRight = std::max(maxRowRight, max.x); + float rowBottom = lastIt->mPosition.y - textLayoutInfo.mScrollOffset.y; + float rowTop = rowBottom - rowSize.height; + + // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards + if(charSelected && blockAlignEnabled) + { + rowRight = std::numeric_limits::max(); + } + newHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom ); + + selectionState = SelectionNone; + + // Still selected? start a new selection + if( charSelected ) + { + // if block-align mode then set rowLeft to min, so it can be clamped afterwards + rowLeft = blockAlignEnabled ? 0.0f : charInfo.mPosition.x - textLayoutInfo.mScrollOffset.x; + rowRight = ( charInfo.mPosition.x - textLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width; + selectionState = SelectionStarted; + } + } + else + { + // build up highlight(s) with this selection data. + rowLeft = std::min( charInfo.mPosition.x - textLayoutInfo.mScrollOffset.x, rowLeft ); + rowRight = std::max( ( charInfo.mPosition.x - textLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight ); + } + } + + lastIt = it++; + } + + // If reached end, and still on selection, then close selection. + if(it == end) + { + if(selectionState == SelectionStarted) + { + // finished selection. + Vector2 min, max; + if(lastIt->mIsNewLineChar) + { + lastIt = std::max( textLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 ); + } + const Size rowSize( mTextViewCharacterPositioning.GetRowRectFromCharacterPosition( lastIt - textLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) ); + maxRowLeft = std::min(maxRowLeft, min.x); + maxRowRight = std::max(maxRowRight, max.x); + float rowBottom = lastIt->mPosition.y - textLayoutInfo.mScrollOffset.y; + float rowTop = rowBottom - rowSize.height; + newHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom ); + } + } + + // Get the top left and bottom right corners. + const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *textLayoutInfo.mCharacterLayoutInfoTable.begin() ); + const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height ); + const Vector2 bottomRight( topLeft.x + textLayoutInfo.mTextSize.width, topLeft.y + textLayoutInfo.mTextSize.height ); + + // Clamp quads so they appear to clip to borders of the whole text. + newHighlightInfo.Clamp2D( topLeft, bottomRight ); + + // For block-align align Further Clamp quads to max left and right extents + if(blockAlignEnabled) + { + // BlockAlign: Will adjust highlight to block: + // i.e. + // H[ello] (top row right = max of all rows right) + // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right) + // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right) + // [text] (bottom row left = min of all rows left) + // (common in SMS messaging selection) + // + // As opposed to the default which is tight text highlighting. + // H[ello] + // [this] + // [is some] + // [text] + // (common in regular text editors/web browser selection) + + newHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) ); + } + } + + return newHighlightInfo; +} + +void TextHighlight::UpdateHighlight( TextHighlight::HighlightInfo& newHighlightInfo ) +{ +// Construct a Mesh with a texture to be used as the highlight 'box' for selected text +// +// Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads. +// +// [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ] +// [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ] +// [ BOTTOM] [ MIDDLE ] [ MIDDLE ] +// [BOTTOM] [ MIDDLE ] +// [BOTTOM] +// +// Each quad is created as 2 triangles. +// Middle is just 1 quad regardless of its size. +// +// (0,0) (0,0) +// 0* *2 0* *2 +// TOP TOP +// 3* *1 3* *1 +// 4* *1 4* *6 +// MIDDLE BOTTOM +// 6* *5 7* *5 +// 6* *8 +// BOTTOM +// 9* *7 +// + + // vertex and triangle buffers should always be present if MeshActor is alive. + //HighlightInfo newHighlightInfo = CalculateHighlightInfo( handlePositionStart, handlePositionEnd ); + MeshData::VertexContainer vertices; + Dali::MeshData::FaceIndices faceIndices; + + if( !newHighlightInfo.mQuadList.empty() ) + { + std::vector::iterator iter = newHighlightInfo.mQuadList.begin(); + std::vector::iterator endIter = newHighlightInfo.mQuadList.end(); + + // vertex position defaults to (0 0 0) + MeshData::Vertex vertex; + // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor. + vertex.nZ = 1.0f; + + for(std::size_t v = 0; iter != endIter; ++iter,v+=4 ) + { + // Add each quad geometry (a sub-selection) to the mesh data. + + // 0-----1 + // |\ | + // | \ A | + // | \ | + // | B \ | + // | \| + // 2-----3 + + QuadCoordinates& quad = *iter; + // top-left (v+0) + vertex.x = quad.min.x; + vertex.y = quad.min.y; + vertices.push_back( vertex ); + + // top-right (v+1) + vertex.x = quad.max.x; + vertex.y = quad.min.y; + vertices.push_back( vertex ); + + // bottom-left (v+2) + vertex.x = quad.min.x; + vertex.y = quad.max.y; + vertices.push_back( vertex ); + + // bottom-right (v+3) + vertex.x = quad.max.x; + vertex.y = quad.max.y; + vertices.push_back( vertex ); + + // triangle A (3, 1, 0) + faceIndices.push_back( v + 3 ); + faceIndices.push_back( v + 1 ); + faceIndices.push_back( v ); + + // triangle B (0, 2, 3) + faceIndices.push_back( v ); + faceIndices.push_back( v + 2 ); + faceIndices.push_back( v + 3 ); + } + + mMeshData.SetVertices( vertices ); + mMeshData.SetFaceIndices( faceIndices ); + + mHighlightMesh.UpdateMeshData(mMeshData); + } +} + +Mesh TextHighlight::CreateHighLightMesh() +{ + mMeshData = MeshData( ); + mMeshData.SetHasNormals( true ); + + mCustomMaterial = Material::New("CustomMaterial"); + mCustomMaterial.SetDiffuseColor( LIGHTBLUE ); + + mMeshData.SetMaterial( mCustomMaterial ); + + mHighlightMesh = Mesh::New( mMeshData ); + + return mHighlightMesh; +} + +void TextHighlight::HighlightInfo::AddQuad( float x1, float y1, float x2, float y2 ) +{ + QuadCoordinates quad(x1, y1, x2, y2); + mQuadList.push_back( quad ); +} + +void TextHighlight::HighlightInfo::Clamp2D(const Vector2& min, const Vector2& max) +{ + for(std::size_t i = 0;i < mQuadList.size(); i++) + { + QuadCoordinates& quad = mQuadList[i]; + + quad.min.Clamp(min, max); + quad.max.Clamp(min, max); + } +} + +} // Internal + +} // namespace Toolkit + +} // namespace Dali + diff --git a/base/dali-toolkit/internal/controls/text-input/text-input-text-highlight-impl.h b/base/dali-toolkit/internal/controls/text-input/text-input-text-highlight-impl.h index ae95f7d..b52cebb 100644 --- a/base/dali-toolkit/internal/controls/text-input/text-input-text-highlight-impl.h +++ b/base/dali-toolkit/internal/controls/text-input/text-input-text-highlight-impl.h @@ -85,12 +85,12 @@ public: * @param[in] x2 right co-ordinate * @param[in] y2 bottom co-ordinate */ - void AddQuad( float x1, float y1, float x2, float y2 ){}; + void AddQuad( float x1, float y1, float x2, float y2 ); /** * Clamps all quads to fit within a min -> max 2D boundary. */ - void Clamp2D(const Vector2& min, const Vector2& max){}; + void Clamp2D(const Vector2& min, const Vector2& max); QuadContainer mQuadList; ///< List of quads (sub-selections that form to create complete selection) }; @@ -99,12 +99,12 @@ public: * Constructor * @param[in] textViewCharacterPositioning TextViewCharacterPositioning to be used for positioning information. */ - TextHighlight( TextViewCharacterPositioning& textViewCharacterPositioning ):mTextViewCharacterPositioning( textViewCharacterPositioning ){}; + TextHighlight( TextViewCharacterPositioning& textViewCharacterPositioning ); /** * Destructor */ - ~TextHighlight(){}; + ~TextHighlight(); /** * Gets the table of the visual text positions which has a flag @@ -119,7 +119,7 @@ public: * @param[in] textLayoutInfo TextView character layout information */ void GetVisualTextSelection(std::vector& selectedVisualText, std::size_t startSelection, std::size_t endSelection, - Toolkit::TextView::TextLayoutInfo& textLayoutInfo){}; + Toolkit::TextView::TextLayoutInfo& textLayoutInfo); /** * Iterates between selection handles and computes the info required to build the highlight mesh @@ -127,19 +127,18 @@ public: * @param[in] handlePositionEnd ending handle position * @return textHighlight target TextHighlight */ - TextHighlight::HighlightInfo CalculateHighlightInfo( std::size_t handlePositionStart, std::size_t handlePositionEnd, Toolkit::TextView::TextLayoutInfo& textLayoutInfo ) - { return HighlightInfo();}; + TextHighlight::HighlightInfo CalculateHighlightInfo( std::size_t handlePositionStart, std::size_t handlePositionEnd, Toolkit::TextView::TextLayoutInfo& textLayoutInfo ); /** * Calculates new Mesh data so highlight moves with selection handles. * @param[in] newHighlightInfo HighlightInfo calculated by CalculateHighlightInfo */ - void UpdateHighlight( TextHighlight::HighlightInfo& newHighlightInfo ){}; + void UpdateHighlight( TextHighlight::HighlightInfo& newHighlightInfo ); /** * Creates the Mesh data needed by the Mesh Actor */ - Mesh CreateHighLightMesh(){return Mesh();}; + Mesh CreateHighLightMesh(); private: diff --git a/base/dali-toolkit/internal/file.list b/base/dali-toolkit/internal/file.list index 1fa0e57..61f3ba0 100644 --- a/base/dali-toolkit/internal/file.list +++ b/base/dali-toolkit/internal/file.list @@ -37,6 +37,7 @@ toolkit_base_src_files = \ $(toolkit_base_src_dir)/controls/table-view/table-view-impl.cpp \ $(toolkit_base_src_dir)/controls/text-input/text-input-decorator-impl.cpp \ $(toolkit_base_src_dir)/controls/text-input/text-input-handles-impl.cpp \ + $(toolkit_base_src_dir)/controls/text-input/text-input-text-highlight-impl.cpp \ $(toolkit_base_src_dir)/controls/text-input/text-input-impl.cpp \ $(toolkit_base_src_dir)/controls/text-input/text-input-popup-impl.cpp \ $(toolkit_base_src_dir)/controls/text-view/relayout-utilities.cpp \ -- 2.7.4