64ab2a25460fc78952cd34e34519fce5d7f9656c
[platform/core/uifw/dali-toolkit.git] / base / dali-toolkit / internal / controls / text-input / text-input-text-highlight-impl.cpp
1 /*
2  * Copyright (c) 2014 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // INTERNAL INCLUDES
19 #include <dali-toolkit/internal/controls/text-input/text-input-text-highlight-impl.h>
20
21 #include <dali/dali.h>
22
23 #include <dali/integration-api/debug.h>
24
25 #include <math.h>
26 #include <sstream>
27 #include <algorithm>
28 #include <libintl.h>
29
30 using namespace Dali;
31 using namespace std;
32
33 namespace
34 {
35   /**
36    * Selection state enumeration (FSM)
37    */
38   enum SelectionState
39   {
40     SelectionNone,                            ///< Currently not encountered selected section.
41     SelectionStarted,                         ///< Encountered selected section
42     SelectionFinished                         ///< Finished selected section
43   };
44
45   const Vector4 LIGHTBLUE( 10.0f/255.0f, 140.0f/255.0f, 210.0f/255.0f, 1.0f );    // todo make this a setting
46
47   const float CHARACTER_THRESHOLD( 2.5f ); // todo check if unified method to do this in Text
48 }
49
50 namespace Dali
51 {
52
53 namespace Toolkit
54 {
55
56 namespace Internal
57 {
58
59 // Default constructor
60 TextHighlight::TextHighlight( TextViewCharacterPositioning& textViewCharacterPositioning ) :
61     mTextViewCharacterPositioning( textViewCharacterPositioning )
62 {
63 }
64
65 TextHighlight::~TextHighlight()
66 {
67 }
68
69 void TextHighlight::GetVisualTextSelection(std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection,
70                                            Toolkit::TextView::TextLayoutInfo& textLayoutInfo )
71 {
72   std::vector<int>::iterator it = textLayoutInfo.mCharacterLogicalToVisualMap.begin();
73   std::vector<int>::iterator startSelectionIt = textLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::min(startSelection, endSelection);
74   std::vector<int>::iterator endSelectionIt = textLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::max(startSelection, endSelection);
75   std::vector<int>::iterator end = textLayoutInfo.mCharacterLogicalToVisualMap.end();
76
77   selectedVisualText.resize( mTextViewCharacterPositioning.GetNumberOfCharactersInText() );
78
79   // Deselect text prior to startSelectionIt
80   for(;it!=startSelectionIt;++it)
81   {
82     selectedVisualText[*it] = false;
83   }
84
85   // Select text from startSelectionIt -> endSelectionIt
86   for(;it!=endSelectionIt;++it)
87   {
88     selectedVisualText[*it] = true;
89   }
90
91   // Deselect text after endSelection
92   for(;it!=end;++it)
93   {
94     selectedVisualText[*it] = false;
95   }
96
97   selectedVisualText.resize( mTextViewCharacterPositioning.GetNumberOfCharactersInText(), false );
98 }
99
100 // Calculate the dimensions of the quads they will make the highlight mesh
101 TextHighlight::HighlightInfo TextHighlight::CalculateHighlightInfo( std::size_t handlePositionStart, std::size_t handlePositionEnd, Toolkit::TextView::TextLayoutInfo& textLayoutInfo )
102 {
103   // At the moment there is no public API to modify the block alignment option.
104   const bool blockAlignEnabled = true;
105
106   TextHighlight::HighlightInfo newHighlightInfo;
107   //newHighlightInfo.mQuadList.clear(); // clear last quad information.
108
109   if ( !mTextViewCharacterPositioning.IsTextEmpty() && !textLayoutInfo.mCharacterLogicalToVisualMap.empty() )
110   {
111     // Get vector of flags representing characters that are selected (true) vs unselected (false).
112     std::vector<bool> selectedVisualText;
113     GetVisualTextSelection(selectedVisualText, handlePositionStart, handlePositionEnd, textLayoutInfo );
114     std::vector<bool>::iterator selectedIt(selectedVisualText.begin());
115     std::vector<bool>::iterator selectedEndIt(selectedVisualText.end());
116
117     SelectionState selectionState = SelectionNone;          ///< Current selection status of cursor over entire text.
118     float rowLeft = 0.0f;
119     float rowRight = 0.0f;
120     // Keep track of the TextView's min/max extents. Should be able to query this from TextView.
121     float maxRowLeft = std::numeric_limits<float>::max();
122     float maxRowRight = 0.0f;
123
124     Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = textLayoutInfo.mCharacterLayoutInfoTable.begin();
125     Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = textLayoutInfo.mCharacterLayoutInfoTable.end();
126
127     Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it;
128
129     // Scan through entire text.
130     while(it != end)
131     {
132       // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
133
134       Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
135       bool charSelected( false );
136       if( selectedIt != selectedEndIt )
137       {
138         charSelected = *selectedIt++;
139       }
140
141       if(selectionState == SelectionNone)
142       {
143         if(charSelected)
144         {
145           selectionState = SelectionStarted;
146           rowLeft = charInfo.mPosition.x - textLayoutInfo.mScrollOffset.x;
147           rowRight = rowLeft + charInfo.mSize.width;
148         }
149       }
150       else if(selectionState == SelectionStarted)
151       {
152         // break selection on:
153         // 1. new line causing selection break. (\n or wordwrap)
154         // 2. character not selected.
155         if(charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD ||
156            !charSelected)
157         {
158           // finished selection.
159           // TODO: TextView should have a table of visual rows, and each character a reference to the row
160           // that it resides on. That way this enumeration is not necessary.
161           Vector2 min, max;
162           if(lastIt->mIsNewLineChar)
163           {
164             // 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.
165             lastIt = std::max( textLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
166           }
167           const Size rowSize( mTextViewCharacterPositioning.GetRowRectFromCharacterPosition( lastIt - textLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
168           maxRowLeft = std::min(maxRowLeft, min.x);
169           maxRowRight = std::max(maxRowRight, max.x);
170           float rowBottom = lastIt->mPosition.y - textLayoutInfo.mScrollOffset.y;
171           float rowTop = rowBottom - rowSize.height;
172
173           // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards
174           if(charSelected && blockAlignEnabled)
175           {
176             rowRight = std::numeric_limits<float>::max();
177           }
178           newHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
179
180           selectionState = SelectionNone;
181
182           // Still selected? start a new selection
183           if( charSelected )
184           {
185             // if block-align mode then set rowLeft to min, so it can be clamped afterwards
186             rowLeft = blockAlignEnabled ? 0.0f : charInfo.mPosition.x - textLayoutInfo.mScrollOffset.x;
187             rowRight = ( charInfo.mPosition.x - textLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width;
188             selectionState = SelectionStarted;
189           }
190         }
191         else
192         {
193           // build up highlight(s) with this selection data.
194           rowLeft = std::min( charInfo.mPosition.x - textLayoutInfo.mScrollOffset.x, rowLeft );
195           rowRight = std::max( ( charInfo.mPosition.x - textLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight );
196         }
197       }
198
199       lastIt = it++;
200     }
201
202     // If reached end, and still on selection, then close selection.
203     if(it == end)
204     {
205       if(selectionState == SelectionStarted)
206       {
207         // finished selection.
208         Vector2 min, max;
209         if(lastIt->mIsNewLineChar)
210         {
211           lastIt = std::max( textLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
212         }
213         const Size rowSize( mTextViewCharacterPositioning.GetRowRectFromCharacterPosition( lastIt - textLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
214         maxRowLeft = std::min(maxRowLeft, min.x);
215         maxRowRight = std::max(maxRowRight, max.x);
216         float rowBottom = lastIt->mPosition.y - textLayoutInfo.mScrollOffset.y;
217         float rowTop = rowBottom - rowSize.height;
218         newHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
219       }
220     }
221
222     // Get the top left and bottom right corners.
223     const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *textLayoutInfo.mCharacterLayoutInfoTable.begin() );
224     const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height );
225     const Vector2 bottomRight( topLeft.x + textLayoutInfo.mTextSize.width, topLeft.y + textLayoutInfo.mTextSize.height );
226
227     // Clamp quads so they appear to clip to borders of the whole text.
228     newHighlightInfo.Clamp2D( topLeft, bottomRight );
229
230     // For block-align align Further Clamp quads to max left and right extents
231     if(blockAlignEnabled)
232     {
233       // BlockAlign: Will adjust highlight to block:
234       // i.e.
235       //   H[ello] (top row right = max of all rows right)
236       // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
237       // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
238       // [text] (bottom row left = min of all rows left)
239       // (common in SMS messaging selection)
240       //
241       // As opposed to the default which is tight text highlighting.
242       //   H[ello]
243       //   [this]
244       // [is some]
245       // [text]
246       // (common in regular text editors/web browser selection)
247
248       newHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) );
249     }
250   }
251
252   return newHighlightInfo;
253 }
254
255 void TextHighlight::UpdateHighlight( TextHighlight::HighlightInfo& newHighlightInfo )
256 {
257 //  Construct a Mesh with a texture to be used as the highlight 'box' for selected text
258 //
259 //  Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
260 //
261 //   [ TOP   ]  [ TOP ]      [TOP ]  [ TOP    ]      [ TOP  ]      [ TOP  ]
262 //  [ MIDDLE ]             [BOTTOM]  [BOTTOM]      [ MIDDLE ]   [ MIDDLE  ]
263 //  [ BOTTOM]                                      [ MIDDLE ]   [ MIDDLE  ]
264 //                                                 [BOTTOM]     [ MIDDLE  ]
265 //                                                              [BOTTOM]
266 //
267 //  Each quad is created as 2 triangles.
268 //  Middle is just 1 quad regardless of its size.
269 //
270 //  (0,0)         (0,0)
271 //     0*    *2     0*       *2
272 //     TOP          TOP
273 //     3*    *1     3*       *1
274 //  4*       *1     4*     *6
275 //     MIDDLE         BOTTOM
276 //  6*       *5     7*     *5
277 //  6*    *8
278 //   BOTTOM
279 //  9*    *7
280 //
281
282   // vertex and triangle buffers should always be present if MeshActor is alive.
283   //HighlightInfo newHighlightInfo = CalculateHighlightInfo( handlePositionStart, handlePositionEnd );
284   MeshData::VertexContainer vertices;
285   Dali::MeshData::FaceIndices faceIndices;
286
287   if( !newHighlightInfo.mQuadList.empty() )
288   {
289     std::vector<QuadCoordinates>::iterator iter = newHighlightInfo.mQuadList.begin();
290     std::vector<QuadCoordinates>::iterator endIter = newHighlightInfo.mQuadList.end();
291
292     // vertex position defaults to (0 0 0)
293     MeshData::Vertex vertex;
294     // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
295     vertex.nZ = 1.0f;
296
297     for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
298     {
299       // Add each quad geometry (a sub-selection) to the mesh data.
300
301       // 0-----1
302       // |\    |
303       // | \ A |
304       // |  \  |
305       // | B \ |
306       // |    \|
307       // 2-----3
308
309       QuadCoordinates& quad = *iter;
310       // top-left (v+0)
311       vertex.x = quad.min.x;
312       vertex.y = quad.min.y;
313       vertices.push_back( vertex );
314
315       // top-right (v+1)
316       vertex.x = quad.max.x;
317       vertex.y = quad.min.y;
318       vertices.push_back( vertex );
319
320       // bottom-left (v+2)
321       vertex.x = quad.min.x;
322       vertex.y = quad.max.y;
323       vertices.push_back( vertex );
324
325       // bottom-right (v+3)
326       vertex.x = quad.max.x;
327       vertex.y = quad.max.y;
328       vertices.push_back( vertex );
329
330       // triangle A (3, 1, 0)
331       faceIndices.push_back( v + 3 );
332       faceIndices.push_back( v + 1 );
333       faceIndices.push_back( v );
334
335       // triangle B (0, 2, 3)
336       faceIndices.push_back( v );
337       faceIndices.push_back( v + 2 );
338       faceIndices.push_back( v + 3 );
339     }
340
341     mMeshData.SetVertices( vertices );
342     mMeshData.SetFaceIndices( faceIndices );
343
344     mHighlightMesh.UpdateMeshData(mMeshData);
345   }
346 }
347
348 Mesh TextHighlight::CreateHighLightMesh()
349 {
350   mMeshData = MeshData( );
351   mMeshData.SetHasNormals( true );
352
353   mCustomMaterial = Material::New("CustomMaterial");
354   mCustomMaterial.SetDiffuseColor( LIGHTBLUE );
355
356   mMeshData.SetMaterial( mCustomMaterial );
357
358   mHighlightMesh = Mesh::New( mMeshData );
359
360   return mHighlightMesh;
361 }
362
363 void TextHighlight::HighlightInfo::AddQuad( float x1, float y1, float x2, float y2 )
364 {
365   QuadCoordinates quad(x1, y1, x2, y2);
366   mQuadList.push_back( quad );
367 }
368
369 void TextHighlight::HighlightInfo::Clamp2D(const Vector2& min, const Vector2& max)
370 {
371   for(std::size_t i = 0;i < mQuadList.size(); i++)
372   {
373     QuadCoordinates& quad = mQuadList[i];
374
375     quad.min.Clamp(min, max);
376     quad.max.Clamp(min, max);
377   }
378 }
379
380 } // Internal
381
382 } // namespace Toolkit
383
384 } // namespace Dali
385