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