Text shaper interface
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / public-api / text / text-controller.cpp
index ceae8ca..58fd536 100644 (file)
 #include <dali-toolkit/public-api/text/logical-model.h>
 #include <dali-toolkit/public-api/text/multi-language-support.h>
 #include <dali-toolkit/public-api/text/script-run.h>
+#include <dali-toolkit/public-api/text/segmentation.h>
 #include <dali-toolkit/public-api/text/shaper.h>
 #include <dali-toolkit/public-api/text/text-view.h>
 #include <dali-toolkit/public-api/text/visual-model.h>
 
 // EXTERNAL INCLUDES
-#include <dali/public-api/text-abstraction/font-client.h>
 #include <limits>
+#include <vector>
+#include <dali/public-api/text-abstraction/font-client.h>
+
+using std::vector;
 
 namespace Dali
 {
@@ -41,11 +45,213 @@ namespace Toolkit
 namespace Text
 {
 
+struct Controller::TextInput
+{
+  // Used to queue input events until DoRelayout()
+  enum EventType
+  {
+    KEYBOARD_FOCUS_GAIN_EVENT,
+    KEYBOARD_FOCUS_LOST_EVENT,
+    TAP_EVENT,
+    GRAB_HANDLE_EVENT
+  };
+
+  union Param
+  {
+    int mInt;
+    unsigned int mUint;
+    float mFloat;
+  };
+
+  struct Event
+  {
+    Event( EventType eventType )
+    : type( eventType )
+    {
+      p1.mInt = 0;
+      p2.mInt = 0;
+    }
+
+    EventType type;
+    Param p1;
+    Param p2;
+    Param p3;
+  };
+
+  enum State
+  {
+    INACTIVE,
+    SELECTING,
+    EDITING
+  };
+
+  TextInput( LogicalModelPtr logicalModel,
+             VisualModelPtr visualModel,
+             DecoratorPtr decorator )
+  : mLogicalModel( logicalModel ),
+    mVisualModel( visualModel ),
+    mDecorator( decorator ),
+    mState( INACTIVE )
+  {
+  }
+
+  /**
+   * @brief Helper to move the cursor, grab handle etc.
+   */
+  bool ProcessTouchEvents()
+  {
+    mDecoratorUpdated = false;
+
+    if( mDecorator )
+    {
+      for( vector<TextInput::Event>::iterator iter = mEventQueue.begin(); iter != mEventQueue.end(); ++iter )
+      {
+        switch( iter->type )
+        {
+          case KEYBOARD_FOCUS_GAIN_EVENT:
+          {
+            OnKeyboardFocus( true );
+            break;
+          }
+          case KEYBOARD_FOCUS_LOST_EVENT:
+          {
+            OnKeyboardFocus( false );
+            break;
+          }
+          case TAP_EVENT:
+          {
+            OnTapEvent( *iter );
+            break;
+          }
+          case GRAB_HANDLE_EVENT:
+          {
+            OnGrabHandleEvent( *iter );
+            break;
+          }
+        }
+      }
+    }
+
+    mEventQueue.clear();
+
+    return mDecoratorUpdated;
+  }
+
+  void OnKeyboardFocus( bool hasFocus )
+  {
+    // TODO
+  }
+
+  void OnTapEvent( const Event& event )
+  {
+    if( 1u == event.p1.mUint )
+    {
+      mState = TextInput::EDITING;
+      mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
+      mDecorator->StartCursorBlink();
+      mDecorator->SetGrabHandleActive( true );
+
+      float xPosition = event.p2.mFloat;
+      float yPosition = event.p3.mFloat;
+      float height(0.0f);
+      GetClosestCursorPosition( xPosition, yPosition, height );
+      mDecorator->SetPosition( PRIMARY_CURSOR, xPosition, yPosition, height );
+
+      mDecoratorUpdated = true;
+    }
+    else if( 2u == event.p1.mUint )
+    {
+      mState = TextInput::SELECTING;
+      mDecorator->SetGrabHandleActive( false );
+      mDecorator->SetSelectionActive( true );
+      mDecoratorUpdated = true;
+    }
+  }
+
+  void OnGrabHandleEvent( const Event& event )
+  {
+    unsigned int state = event.p1.mUint;
+
+    if( GRAB_HANDLE_PRESSED == state )
+    {
+      float xPosition = event.p2.mFloat;
+      float yPosition = event.p3.mFloat;
+      float height(0.0f);
+
+      GetClosestCursorPosition( xPosition, yPosition, height );
+
+      mDecorator->SetPosition( PRIMARY_CURSOR, xPosition, yPosition, height );
+      mDecoratorUpdated = true;
+    }
+  }
+
+  void GetClosestCursorPosition( float& x, float& y, float& height )
+  {
+    // TODO - Look at LineRuns first
+
+    Text::Length numberOfGlyphs = mVisualModel->GetNumberOfGlyphs();
+    if( 0 == numberOfGlyphs )
+    {
+      return;
+    }
+
+    Vector<GlyphInfo> glyphs;
+    glyphs.Resize( numberOfGlyphs );
+    mVisualModel->GetGlyphs( &glyphs[0], 0, numberOfGlyphs );
+
+    std::vector<Vector2> positions;
+    positions.resize( numberOfGlyphs );
+    mVisualModel->GetGlyphPositions( &positions[0], 0, numberOfGlyphs );
+
+    unsigned int closestGlyph = 0;
+    float closestDistance = std::numeric_limits<float>::max();
+
+    for( unsigned int i=0; i<glyphs.Count(); ++i )
+    {
+      float glyphX = positions[i].x + glyphs[i].width*0.5f;
+      float glyphY = positions[i].y + glyphs[i].height*0.5f;
+
+      float distanceToGlyph = fabsf( glyphX - x ) + fabsf( glyphY - y );
+
+      if( distanceToGlyph < closestDistance )
+      {
+        closestDistance = distanceToGlyph;
+        closestGlyph = i;
+      }
+    }
+
+    // TODO - Consider RTL languages
+    x = positions[closestGlyph].x + glyphs[closestGlyph].width;
+    y = 0.0f;
+
+    FontMetrics metrics;
+    TextAbstraction::FontClient::Get().GetFontMetrics( glyphs[closestGlyph].fontId, metrics );
+    height = metrics.height; // TODO - Fix for multi-line
+  }
+
+  LogicalModelPtr mLogicalModel;
+  VisualModelPtr  mVisualModel;
+  DecoratorPtr    mDecorator;
+
+  State mState;
+
+  /**
+   * This is used to delay handling events until after the model has been updated.
+   * The number of updates to the model is minimized to improve performance.
+   */
+  vector<Event> mEventQueue; ///< The queue of touch events etc.
+
+  bool mDecoratorUpdated;
+};
+
 struct Controller::Impl
 {
-  Impl()
-  : mNewText(),
-    mOperations( NO_OPERATION )
+  Impl( ControlInterface& controlInterface )
+  : mControlInterface( controlInterface ),
+    mNewText(),
+    mOperations( NO_OPERATION ),
+    mControlSize(),
+    mTextInput( NULL )
   {
     mLogicalModel = LogicalModel::New();
     mVisualModel  = VisualModel::New();
@@ -55,6 +261,13 @@ struct Controller::Impl
     mFontClient = TextAbstraction::FontClient::Get();
   }
 
+  ~Impl()
+  {
+    delete mTextInput;
+  }
+
+  ControlInterface& mControlInterface;
+
   std::string mNewText;
 
   LogicalModelPtr mLogicalModel;
@@ -67,11 +280,16 @@ struct Controller::Impl
   TextAbstraction::FontClient mFontClient;
 
   OperationsMask mOperations;
+
+  Size mControlSize;
+
+  // Avoid allocating everything for text input until EnableTextInput()
+  Controller::TextInput* mTextInput;
 };
 
-ControllerPtr Controller::New()
+ControllerPtr Controller::New( ControlInterface& controlInterface )
 {
-  return ControllerPtr( new Controller() );
+  return ControllerPtr( new Controller( controlInterface ) );
 }
 
 void Controller::SetText( const std::string& text )
@@ -79,6 +297,22 @@ void Controller::SetText( const std::string& text )
   // Keep until size negotiation
   mImpl->mNewText = text;
   mImpl->mOperations = ALL_OPERATIONS;
+
+  if( mImpl->mTextInput )
+  {
+    // Cancel previously queued events
+    mImpl->mTextInput->mEventQueue.clear();
+
+    // TODO - Hide selection decorations
+  }
+}
+
+void Controller::EnableTextInput( DecoratorPtr decorator )
+{
+  if( !mImpl->mTextInput )
+  {
+    mImpl->mTextInput = new TextInput( mImpl->mLogicalModel, mImpl->mVisualModel, decorator );
+  }
 }
 
 bool Controller::Relayout( const Vector2& size )
@@ -89,19 +323,25 @@ bool Controller::Relayout( const Vector2& size )
     return false;
   }
 
-  bool viewUpdated = false;
+  bool updated = false;
 
-  if( size != mControlSize )
+  if( size != mImpl->mControlSize )
   {
-    viewUpdated = DoRelayout( size, mImpl->mOperations );
+    updated = DoRelayout( size, mImpl->mOperations );
 
     // Do not re-do any operation until something changes.
     mImpl->mOperations = NO_OPERATION;
 
-    mControlSize = size;
+    mImpl->mControlSize = size;
   }
 
-  return viewUpdated;
+  if( mImpl->mTextInput )
+  {
+    // Move the cursor, grab handle etc.
+    updated = mImpl->mTextInput->ProcessTouchEvents() || updated;
+  }
+
+  return updated;
 }
 
 bool Controller::DoRelayout( const Vector2& size, OperationsMask operations )
@@ -132,6 +372,21 @@ bool Controller::DoRelayout( const Vector2& size, OperationsMask operations )
     text.clear();
   }
 
+  Vector<LineBreakInfo> lineBreakInfo;
+  if( GET_LINE_BREAKS & operations )
+  {
+    // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
+    // calculate the bidirectional info for each 'paragraph'.
+    // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
+    // is not shaped together).
+    lineBreakInfo.Resize( characterCount, TextAbstraction::LINE_NO_BREAK );
+
+    SetLineBreakInfo( utf32Characters,
+                      lineBreakInfo );
+
+    mImpl->mLogicalModel->SetLineBreakInfo( lineBreakInfo.Begin(), characterCount );
+  }
+
   const bool getScripts = GET_SCRIPTS & operations;
   const bool validateFonts = VALIDATE_FONTS & operations;
 
@@ -147,6 +402,7 @@ bool Controller::DoRelayout( const Vector2& size, OperationsMask operations )
     {
       // Retrieves the scripts used in the text.
       multilanguageSupport.SetScripts( utf32Characters,
+                                       lineBreakInfo,
                                        scripts );
 
       // Sets the scripts into the model.
@@ -166,17 +422,6 @@ bool Controller::DoRelayout( const Vector2& size, OperationsMask operations )
     }
   }
 
-  Vector<LineBreakInfo> lineBreakInfo;
-  if( GET_LINE_BREAKS & operations )
-  {
-    // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
-    // calculate the bidirectional info for each 'paragraph'.
-    // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
-    // is not shaped together).
-    lineBreakInfo.Resize( characterCount, TextAbstraction::LINE_NO_BREAK );
-    mImpl->mLogicalModel->SetLineBreakInfo( lineBreakInfo.Begin(), characterCount );
-  }
-
   Vector<GlyphInfo> glyphs;
   Vector<CharacterIndex> characterIndices;
   Vector<Length> charactersPerGlyph;
@@ -194,7 +439,7 @@ bool Controller::DoRelayout( const Vector2& size, OperationsMask operations )
 
   if( GET_GLYPH_METRICS & operations )
   {
-    TextAbstraction::FontClient::Get().GetGlyphMetrics( glyphs.Begin(), glyphs.Count() );
+    mImpl->mFontClient.GetGlyphMetrics( glyphs.Begin(), glyphs.Count() );
   }
 
   if( LAYOUT & operations )
@@ -210,7 +455,7 @@ bool Controller::DoRelayout( const Vector2& size, OperationsMask operations )
       mImpl->mVisualModel->GetGlyphs( glyphs.Begin(),
                                       0u,
                                       numberOfGlyphs );
-      
+
       mImpl->mVisualModel->GetGlyphToCharacterMap( characterIndices.Begin(),
                                                    0u,
                                                    numberOfGlyphs );
@@ -300,16 +545,78 @@ LayoutEngine& Controller::GetLayoutEngine()
   return mImpl->mLayoutEngine;
 }
 
+void Controller::RequestRelayout()
+{
+  mImpl->mControlInterface.RequestTextRelayout();
+}
+
+void Controller::KeyboardFocusGainEvent()
+{
+  DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected KeyboardFocusGainEvent" );
+
+  if( mImpl->mTextInput )
+  {
+    TextInput::Event event( TextInput::KEYBOARD_FOCUS_GAIN_EVENT );
+    mImpl->mTextInput->mEventQueue.push_back( event );
+
+    RequestRelayout();
+  }
+}
+
+void Controller::KeyboardFocusLostEvent()
+{
+  DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected KeyboardFocusLostEvent" );
+
+  if( mImpl->mTextInput )
+  {
+    TextInput::Event event( TextInput::KEYBOARD_FOCUS_LOST_EVENT );
+    mImpl->mTextInput->mEventQueue.push_back( event );
+
+    RequestRelayout();
+  }
+}
+
+void Controller::TapEvent( unsigned int tapCount, float x, float y )
+{
+  DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected TapEvent" );
+
+  if( mImpl->mTextInput )
+  {
+    TextInput::Event event( TextInput::TAP_EVENT );
+    event.p1.mUint = tapCount;
+    event.p2.mFloat = x;
+    event.p3.mFloat = y;
+    mImpl->mTextInput->mEventQueue.push_back( event );
+
+    RequestRelayout();
+  }
+}
+
+void Controller::GrabHandleEvent( GrabHandleState state, float x, float y )
+{
+  DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected GrabHandleEvent" );
+
+  if( mImpl->mTextInput )
+  {
+    TextInput::Event event( TextInput::GRAB_HANDLE_EVENT );
+    event.p1.mUint  = state;
+    event.p2.mFloat = x;
+    event.p3.mFloat = y;
+    mImpl->mTextInput->mEventQueue.push_back( event );
+
+    RequestRelayout();
+  }
+}
+
 Controller::~Controller()
 {
   delete mImpl;
 }
 
-Controller::Controller()
-: mImpl( NULL ),
-  mControlSize()
+Controller::Controller( ControlInterface& controlInterface )
+: mImpl( NULL )
 {
-  mImpl = new Controller::Impl();
+  mImpl = new Controller::Impl( controlInterface );
 }
 
 } // namespace Text