It updates the double tap and long press event on a text area with no text.
i.e. On a position clearly after the last character of the text.
* Double Tap : The cursor is placed at the beginning or at the end of the text.
* Long Press : The cursor is placed at the beginning or at the end of the text and shows the text's selection popup.
Change-Id: Iaf9ea817a515781d20c87f60991e1996d5440a62
Signed-off-by: Victor Cebollada <v.cebollada@samsung.com>
tet_result(TET_PASS);
END_TEST;
}
+
+int UtcDaliTextControllerSetGetTapLongPressAction(void)
+{
+ tet_infoline(" UtcDaliTextControllerSetGetTapLongPressAction");
+ ToolkitTestApplication application;
+
+ // Creates a text controller.
+ ControllerPtr controller = Controller::New();
+
+ DALI_TEST_CHECK( controller );
+
+ // Test first with no decorator.
+
+ DALI_TEST_EQUALS( Controller::NoTextTap::NO_ACTION, controller->GetNoTextDoubleTapAction(), TEST_LOCATION );
+ controller->SetNoTextDoubleTapAction( Controller::NoTextTap::HIGHLIGHT );
+ DALI_TEST_EQUALS( Controller::NoTextTap::NO_ACTION, controller->GetNoTextDoubleTapAction(), TEST_LOCATION );
+
+ DALI_TEST_EQUALS( Controller::NoTextTap::NO_ACTION, controller->GetNoTextLongPressAction(), TEST_LOCATION );
+ controller->SetNoTextLongPressAction( Controller::NoTextTap::HIGHLIGHT );
+ DALI_TEST_EQUALS( Controller::NoTextTap::NO_ACTION, controller->GetNoTextLongPressAction(), TEST_LOCATION );
+
+ // Add a decorator and re-test.
+
+ // Creates a decorator.
+ Text::DecoratorPtr decorator = Text::Decorator::New( *controller, *controller );
+
+ // Enables the text input.
+ controller->EnableTextInput( decorator );
+
+ DALI_TEST_EQUALS( Controller::NoTextTap::NO_ACTION, controller->GetNoTextDoubleTapAction(), TEST_LOCATION );
+ controller->SetNoTextDoubleTapAction( Controller::NoTextTap::HIGHLIGHT );
+ DALI_TEST_EQUALS( Controller::NoTextTap::HIGHLIGHT, controller->GetNoTextDoubleTapAction(), TEST_LOCATION );
+
+ DALI_TEST_EQUALS( Controller::NoTextTap::SHOW_SELECTION_POPUP, controller->GetNoTextLongPressAction(), TEST_LOCATION ); // The default is SHOW_SELECTION_POPUP
+ controller->SetNoTextLongPressAction( Controller::NoTextTap::HIGHLIGHT );
+ DALI_TEST_EQUALS( Controller::NoTextTap::HIGHLIGHT, controller->GetNoTextLongPressAction(), TEST_LOCATION );
+
+ END_TEST;
+}
/*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
// Tests the following functions.
//
// LineIndex GetClosestLine( VisualModelPtr visualModel,
-// float visualY )
+// float visualY,
+// bool& isLineHit )
// CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
// LogicalModelPtr logicalModel,
// MetricsPtr metrics,
// float visualX,
-// float visualY )
+// float visualY,
+// CharacterHitTest::Mode mode,
+// bool& isCharacterHit )
//////////////////////////////////////////////////////////
unsigned int numberOfTests; ///< The number of tests.
float* visualY; ///< The visual 'y' position for each test.
LineIndex* lineIndex; ///< The expected line index for each test.
+ bool* isLineHit; ///< The expected line hit value for each test.
};
struct GetClosestCursorIndexData
{
+ std::string description; ///< Description of the test.
+ std::string text; ///< Input text.
+ unsigned int numberOfTests; ///< The number of tests.
+ float* visualX; ///< The visual 'x' position for each test.
+ float* visualY; ///< The visual 'y' position for each test.
+ CharacterHitTest::Mode* mode; ///< The type of hit test.
+ CharacterIndex* logicalIndex; ///< The expected logical cursor index for each test.
+ bool* isCharacterHit; ///< The expected character hit value for each test.
+};
+
+struct GetCursorPositionData
+{
+ std::string description; ///< Description of the test.
+ std::string text; ///< Input text.
+ unsigned int numberOfTests; ///< The number of tests.
+ CharacterIndex* logicalIndex; ///< The logical cursor index for each test.
+ float* visualX; ///< The expected visual 'x' position for each test.
+ float* visualY; ///< The expected visual 'y' position for each test.
+};
+
+struct FindSelectionIndicesData
+{
std::string description; ///< Description of the test.
std::string text; ///< Input text.
unsigned int numberOfTests; ///< The number of tests.
float* visualX; ///< The visual 'x' position for each test.
float* visualY; ///< The visual 'y' position for each test.
- CharacterIndex* logicalIndex; ///< The expected logical cursor index for each test.
+ bool* found; ///< Whether selection indices are found.
+ CharacterIndex* startIndex; ///< The expected start cursor index for each test.
+ CharacterIndex* endIndex; ///< The expected end cursor index for each test.
+ CharacterIndex* noTextHitIndex; ///< The expected character index when there is no hit.
};
bool GetClosestLineTest( const GetClosestLineData& data )
visualModel,
metrics );
- for( unsigned int index = 0u; index < data.numberOfTests; ++index )
+ for( unsigned int index = 0; index < data.numberOfTests; ++index )
{
+ bool isLineHit = false;
const LineIndex lineIndex = GetClosestLine( visualModel,
- data.visualY[index] );
+ data.visualY[index],
+ isLineHit );
if( lineIndex != data.lineIndex[index] )
{
std::cout << " test " << index << " failed. Different line index : " << lineIndex << ", expected : " << data.lineIndex[index] << std::endl;
return false;
}
+ if( isLineHit != data.isLineHit[index] )
+ {
+ std::cout << " test " << index << " failed. Different line hit value : " << isLineHit << ", expected : " << data.isLineHit[index] << std::endl;
+ return false;
+ }
}
return true;
visualModel,
metrics );
- for( unsigned int index = 0u; index < data.numberOfTests; ++index )
+ for( unsigned int index = 0; index < data.numberOfTests; ++index )
{
+ bool isCharacterHit = false;
const CharacterIndex logicalCursorIndex = GetClosestCursorIndex( visualModel,
logicalModel,
metrics,
data.visualX[index],
- data.visualY[index] );
+ data.visualY[index],
+ data.mode[index],
+ isCharacterHit );
if( logicalCursorIndex != data.logicalIndex[index] )
{
std::cout << " test " << index << " failed. Different logical cursor index : " << logicalCursorIndex << ", expected : " << data.logicalIndex[index] << std::endl;
return false;
}
+ if( isCharacterHit != data.isCharacterHit[index] )
+ {
+ std::cout << " test " << index << " failed. Different character hit value : " << isCharacterHit << ", expected : " << data.isCharacterHit[index] << std::endl;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool GetCursorPositionTest( const GetCursorPositionData& data )
+{
+ std::cout << " testing : " << data.description << std::endl;
+
+ // 1) Create the model.
+ LogicalModelPtr logicalModel;
+ VisualModelPtr visualModel;
+ MetricsPtr metrics;
+ Size textArea(400.f, 600.f);
+ Size layoutSize;
+
+ Vector<FontDescriptionRun> fontDescriptionRuns;
+ LayoutOptions options;
+ CreateTextModel( data.text,
+ textArea,
+ fontDescriptionRuns,
+ options,
+ layoutSize,
+ logicalModel,
+ visualModel,
+ metrics );
+
+ for( unsigned int index = 0; index < data.numberOfTests; ++index )
+ {
+ CursorInfo cursorInfo;
+ GetCursorPosition( visualModel,
+ logicalModel,
+ metrics,
+ data.logicalIndex[index],
+ cursorInfo );
+
+ if( cursorInfo.primaryPosition.x != data.visualX[index] )
+ {
+ std::cout << " test " << index << " failed. Different 'x' cursor position : " << cursorInfo.primaryPosition.x << ", expected : " << data.visualX[index] << std::endl;
+ return false;
+ }
+ if( cursorInfo.primaryPosition.y != data.visualY[index] )
+ {
+ std::cout << " test " << index << " failed. Different 'y' cursor position : " << cursorInfo.primaryPosition.y << ", expected : " << data.visualY[index] << std::endl;
+ return false;
+ }
}
return true;
}
+bool FindSelectionIndicesTest( const FindSelectionIndicesData& data )
+{
+ std::cout << " testing : " << data.description << std::endl;
+
+ // 1) Create the model.
+ LogicalModelPtr logicalModel;
+ VisualModelPtr visualModel;
+ MetricsPtr metrics;
+ Size textArea(400.f, 600.f);
+ Size layoutSize;
+
+ Vector<FontDescriptionRun> fontDescriptionRuns;
+ LayoutOptions options;
+ CreateTextModel( data.text,
+ textArea,
+ fontDescriptionRuns,
+ options,
+ layoutSize,
+ logicalModel,
+ visualModel,
+ metrics );
+
+ for( unsigned int index = 0; index < data.numberOfTests; ++index )
+ {
+ CharacterIndex startIndex = 0;
+ CharacterIndex endIndex = 0;
+ CharacterIndex noTextHitIndex = 0;
+ const bool found = FindSelectionIndices( visualModel,
+ logicalModel,
+ metrics,
+ data.visualX[index],
+ data.visualY[index],
+ startIndex,
+ endIndex,
+ noTextHitIndex );
+
+ if( found != data.found[index] )
+ {
+ std::cout << " test " << index << " failed. Different found value : " << found << ", expected : " << data.found[index] << std::endl;
+ return false;
+ }
+ if( startIndex != data.startIndex[index] )
+ {
+ std::cout << " test " << index << " failed. Different start index : " << startIndex << ", expected : " << data.startIndex[index] << std::endl;
+ return false;
+ }
+ if( endIndex != data.endIndex[index] )
+ {
+ std::cout << " test " << index << " failed. Different end index : " << endIndex << ", expected : " << data.endIndex[index] << std::endl;
+ return false;
+ }
+ if( noTextHitIndex != data.noTextHitIndex[index] )
+ {
+ std::cout << " test " << index << " failed. Different no text hit index : " << noTextHitIndex << ", expected : " << data.noTextHitIndex[index] << std::endl;
+ return false;
+ }
+ }
+ return true;
+}
+
} // namespace
//////////////////////////////////////////////////////////
tet_infoline(" UtcDaliGetClosestLine");
float visualY01[] = { -4.f, 3.f, 1000.f };
- LineIndex lineIndices01[] = { 0u, 0u, 0u };
+ LineIndex lineIndices01[] = { 0, 0, 0 };
+ bool isLineHit01[] = { false, false, false };
float visualY02[] = { -4.f, 3.f, 1000.f };
- LineIndex lineIndices02[] = { 0u, 0u, 0u };
+ LineIndex lineIndices02[] = { 0, 0, 0 };
+ bool isLineHit02[] = { false, true, false };
float visualY03[] = { -4.f, 11.f, 30.f, 51.f, 68.f, 87.f, 109.f, 130.f };
- LineIndex lineIndices03[] = { 0u, 0u, 1u, 2u, 3u, 4u, 5u, 5u };
+ LineIndex lineIndices03[] = { 0, 0, 1u, 2u, 3u, 4u, 5u, 5u };
+ bool isLineHit03[] = { false, true, true, true, true, true, true, false };
struct GetClosestLineData data[] =
{
3u,
visualY01,
lineIndices01,
+ isLineHit01
},
{
"Single line text.",
3u,
visualY02,
lineIndices02,
+ isLineHit02
},
{
"Multi-line text.",
"חלךmnoצמםpqrפרףstuדאוvwxהסתyzטזץ",
8u,
visualY03,
- lineIndices03
+ lineIndices03,
+ isLineHit03
}
};
const unsigned int numberOfTests = 3u;
- for( unsigned int index = 0u; index < numberOfTests; ++index )
+ for( unsigned int index = 0; index < numberOfTests; ++index )
{
ToolkitTestApplication application;
if( !GetClosestLineTest( data[index] ) )
int UtcDaliGetClosestCursorIndex(void)
{
tet_infoline(" UtcDaliGetClosestCursorIndex");
+
float visualX01[] = { -100.f };
float visualY01[] = { -100.f };
- CharacterIndex logicalIndex01[] = { 0u };
+ CharacterHitTest::Mode mode01[] = { CharacterHitTest::TAP };
+ CharacterIndex logicalIndex01[] = { 0 };
+ bool isCharacterHit01[] = { false };
float visualX02[] = { -100.f, 1000.f, 60.f, 79.f, 83.f, 148.f, 99.f };
float visualY02[] = { -100.f, 1000.f, 12.f, 12.f, 12.f, 12.f, 12.f };
- CharacterIndex logicalIndex02[] = { 0u, 21u, 7u, 10u, 11u, 13u, 20u };
+ CharacterHitTest::Mode mode02[] = { CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP };
+ CharacterIndex logicalIndex02[] = { 0, 21u, 7u, 10u, 11u, 13u, 20u };
+ bool isCharacterHit02[] = { false, false, true, true, true, true, true };
float visualX03[] = { 19.f, 104.f, -2.f, 127.f };
float visualY03[] = { 12.f, 12.f, 12.f, 12.f };
- CharacterIndex logicalIndex03[] = { 3u, 12u, 0u, 18u };
+ CharacterHitTest::Mode mode03[] = { CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP };
+ CharacterIndex logicalIndex03[] = { 3u, 12u, 0, 18u };
+ bool isCharacterHit03[] = { true, true, false, false };
// 0 5 _ 6 11 12
// Hello world \n
float visualY04[] = { -100.f, 12.f, 12.f, 12.f, 12.f,
30.f, 30.f, 30.f, 30.f, 30.f,
50.f, 50.f, 50.f, 50.f, 50.f, 50.f, 50.f };
- CharacterIndex logicalIndex04[] = { 0u, 5u, 6u, 11u, 11u,
+ CharacterHitTest::Mode mode04[] = { CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+ CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+ CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP };
+ CharacterIndex logicalIndex04[] = { 0, 5u, 6u, 11u, 11u,
12u, 16u, 17u, 21u, 21u,
22u, 25u, 31u, 32u, 34u, 40u, 40u,
41u };
+ bool isCharacterHit04[] = { false, true, true, false, false,
+ false, true, true, true, false,
+ false, true, true, true, true, true, false };
// 0 10 20 30 40 46
// abcשנבdefג קכghiעיןjk lחלךmnoצמם pqrפרףstuד אוvwxה
67.f, 67.f, 67.f, 67.f, 67.f, 67.f,
87.f, 87.f, 87.f, 87.f, 87.f, 87.f,
107.f, 107.f, 107.f, 107.f, 107.f };
- CharacterIndex logicalIndex05[] = { 0u, 10u, 20u, 30u, 40u, 45u,
+ CharacterHitTest::Mode mode05[] = { CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+ CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+ CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+ CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+ CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+ CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP };
+ CharacterIndex logicalIndex05[] = { 0, 10u, 20u, 30u, 40u, 45u,
46u, 50u, 60u, 70u, 80u, 92u,
93u, 100u, 110u, 120u, 130u, 138u,
139u, 150u, 160u, 170u, 180u, 185u,
186u, 190u, 200u, 210u, 220u, 232u,
233u, 240u, 250u, 260u, 265u };
+ bool isCharacterHit05[] = { false, true, true, true, true, false,
+ false, true, true, true, true, false,
+ false, true, true, true, true, false,
+ false, true, true, true, true, false,
+ false, true, true, true, true, false,
+ false, true, true, true, false };
// 0 10 20 30 40 46
// שנבabcגקכd efעיןghiחל ךjklצמםmno פרףpqrדאוs tuהסתv
67.f, 67.f, 67.f, 67.f, 67.f, 67.f,
87.f, 87.f, 87.f, 87.f, 87.f, 87.f,
107.f, 107.f, 107.f, 107.f, 107.f };
- CharacterIndex logicalIndex06[] = { 0u, 10u, 20u, 30u, 40u, 45u,
+ CharacterHitTest::Mode mode06[] = { CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+ CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+ CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+ CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+ CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP,
+ CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP, CharacterHitTest::TAP };
+ CharacterIndex logicalIndex06[] = { 0, 10u, 20u, 30u, 40u, 45u,
46u, 50u, 60u, 70u, 80u, 92u,
93u, 100u, 110u, 120u, 130u, 138u,
139u, 150u, 160u, 170u, 180u, 185u,
186u, 190u, 200u, 210u, 220u, 231u,
232u, 240u, 250u, 260u, 265u };
+ bool isCharacterHit06[] = { false, true, true, true, true, false,
+ false, true, true, true, true, false,
+ false, true, true, true, true, false,
+ false, true, true, true, true, false,
+ false, true, true, true, true, false,
+ false, true, true, true, false };
+
+ float visualX07[] = { 395.f };
+ float visualY07[] = { 12.f };
+ CharacterHitTest::Mode mode07[] = { CharacterHitTest::TAP };
+ CharacterIndex logicalIndex07[] = { 1u };
+ bool isCharacterHit07[] = { true };
+
+ float visualX08[] = { 7.f };
+ float visualY08[] = { 12.f };
+ CharacterHitTest::Mode mode08[] = { CharacterHitTest::TAP };
+ CharacterIndex logicalIndex08[] = { 1u };
+ bool isCharacterHit08[] = { true };
struct GetClosestCursorIndexData data[] =
{
1u,
visualX01,
visualY01,
- logicalIndex01
+ mode01,
+ logicalIndex01,
+ isCharacterHit01
},
{
"Single line text.",
7u,
visualX02,
visualY02,
- logicalIndex02
+ mode02,
+ logicalIndex02,
+ isCharacterHit02
},
{
"Single line with ligatures",
4u,
visualX03,
visualY03,
- logicalIndex03
+ mode03,
+ logicalIndex03,
+ isCharacterHit03
},
{
"Multiline. Single line paragraphs",
17u,
visualX04,
visualY04,
- logicalIndex04
+ mode04,
+ logicalIndex04,
+ isCharacterHit04
},
{
"Multiline. Single bidirectional paragraph, starts LTR, wrapped lines",
35u,
visualX05,
visualY05,
- logicalIndex05
+ mode05,
+ logicalIndex05,
+ isCharacterHit05
},
{
"Multiline. Single bidirectional paragraph, starts RTL, wrapped lines",
35u,
visualX06,
visualY06,
- logicalIndex06
+ mode06,
+ logicalIndex06,
+ isCharacterHit06
+ },
+ {
+ "Testing complex characters. Arabic ligatures",
+ "الأَبْجَدِيَّة العَرَبِيَّة",
+ 1u,
+ visualX07,
+ visualY07,
+ mode07,
+ logicalIndex07,
+ isCharacterHit07
+ },
+ {
+ "Testing complex characters. Latin ligatures",
+ "fi ligature",
+ 1u,
+ visualX08,
+ visualY08,
+ mode08,
+ logicalIndex08,
+ isCharacterHit08
}
};
- const unsigned int numberOfTests = 6u;
+ const unsigned int numberOfTests = 8u;
- for( unsigned int index = 0u; index < numberOfTests; ++index )
+ for( unsigned int index = 0; index < numberOfTests; ++index )
{
ToolkitTestApplication application;
if( !GetClosestCursorIndexTest( data[index] ) )
tet_result(TET_PASS);
END_TEST;
}
+
+int UtcDaliGetCursorPosition(void)
+{
+ tet_infoline(" UtcDaliGetCursorPosition");
+
+ float visualX08[] = { 5.f };
+ float visualY08[] = { 0.f };
+ CharacterIndex logicalIndex08[] = { 1u };
+
+ struct GetCursorPositionData data[] =
+ {
+ {
+ "Testing complex characters. Latin ligatures",
+ "fi ligature",
+ 1u,
+ logicalIndex08,
+ visualX08,
+ visualY08,
+ }
+ };
+ const unsigned int numberOfTests = 1u;
+
+ for( unsigned int index = 0; index < numberOfTests; ++index )
+ {
+ ToolkitTestApplication application;
+ if( !GetCursorPositionTest( data[index] ) )
+ {
+ tet_result(TET_FAIL);
+ }
+ }
+
+ tet_result(TET_PASS);
+ END_TEST;
+}
+
+int UtcDaliFindSelectionIndices(void)
+{
+ tet_infoline(" UtcDaliFindSelectionIndices");
+
+ float visualX01[] = { -100.f };
+ float visualY01[] = { -100.f };
+ bool found01[] = { false };
+ CharacterIndex startIndex01[] = { 0 };
+ CharacterIndex endIndex01[] = { 0 };
+ CharacterIndex noHitText01[] = { 0 };
+
+ float visualX02[] = { -100.f, 1000.f, 1000.f };
+ float visualY02[] = { -100.f, 12.f, 1000.f };
+ bool found02[] = { false, false, false };
+ CharacterIndex startIndex02[] = { 0, 6u, 6u };
+ CharacterIndex endIndex02[] = { 5u, 11u, 11u };
+ CharacterIndex noHitText02[] = { 0, 11u, 11u };
+
+ float visualX03[] = { 70.f };
+ float visualY03[] = { 12.f };
+ bool found03[] = { true };
+ CharacterIndex startIndex03[] = { 6u };
+ CharacterIndex endIndex03[] = { 11u };
+ CharacterIndex noHitText03[] = { 0u };
+
+ float visualX04[] = { 132.f };
+ float visualY04[] = { 12.f };
+ bool found04[] = { true };
+ CharacterIndex startIndex04[] = { 12u };
+ CharacterIndex endIndex04[] = { 16u };
+ CharacterIndex noHitText04[] = { 0u };
+
+ float visualX05[] = { 1.f };
+ float visualY05[] = { 12.f };
+ bool found05[] = { true };
+ CharacterIndex startIndex05[] = { 0 };
+ CharacterIndex endIndex05[] = { 1u };
+ CharacterIndex noHitText05[] = { 0 };
+
+ float visualX06[] = { 10.f };
+ float visualY06[] = { 12.f };
+ bool found06[] = { true };
+ CharacterIndex startIndex06[] = { 0 };
+ CharacterIndex endIndex06[] = { 1u };
+ CharacterIndex noHitText06[] = { 0u };
+
+ struct FindSelectionIndicesData data[] =
+ {
+ {
+ "void text",
+ "",
+ 1u,
+ visualX01,
+ visualY01,
+ found01,
+ startIndex01,
+ endIndex01,
+ noHitText01
+ },
+ {
+ "touch out of text's boundaries",
+ "Hello world",
+ 3u,
+ visualX02,
+ visualY02,
+ found02,
+ startIndex02,
+ endIndex02,
+ noHitText02
+ },
+ {
+ "touch on the text",
+ "Hello world demo",
+ 1u,
+ visualX03,
+ visualY03,
+ found03,
+ startIndex03,
+ endIndex03,
+ noHitText03
+ },
+ {
+ "touch on the new paragraph character at the end of line",
+ "Hello world demo\n",
+ 1u,
+ visualX04,
+ visualY04,
+ found04,
+ startIndex04,
+ endIndex04,
+ noHitText04
+ },
+ {
+ "touch on a white space character. is the unique character of the line",
+ " ",
+ 1u,
+ visualX05,
+ visualY05,
+ found05,
+ startIndex05,
+ endIndex05,
+ noHitText05
+ },
+ {
+ "touch on a white space character. is between two words",
+ "h ello",
+ 1u,
+ visualX06,
+ visualY06,
+ found06,
+ startIndex06,
+ endIndex06,
+ noHitText06
+ },
+ };
+ const unsigned int numberOfTests = 6u;
+
+ for( unsigned int index = 0; index < numberOfTests; ++index )
+ {
+ ToolkitTestApplication application;
+ if( !FindSelectionIndicesTest( data[index] ) )
+ {
+ tet_result(TET_FAIL);
+ }
+ }
+
+ tet_result(TET_PASS);
+ END_TEST;
+}
/*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include <dali/public-api/rendering/renderer.h>
#include <dali/devel-api/adaptor-framework/clipboard.h>
#include <dali/integration-api/events/key-event-integ.h>
+#include <dali/integration-api/events/touch-event-integ.h>
#include <dali/integration-api/events/tap-gesture-event.h>
+#include <dali/integration-api/events/pan-gesture-event.h>
#include <dali-toolkit-test-suite-utils.h>
#include <dali-toolkit/dali-toolkit.h>
#include <dali-toolkit/devel-api/controls/text-controls/text-editor-devel.h>
const unsigned int CURSOR_BLINK_INTERVAL = 500u; // Cursor blink interval
const float TO_MILLISECONDS = 1000.f;
const float TO_SECONDS = 1.f / TO_MILLISECONDS;
+const float RENDER_FRAME_INTERVAL = 16.66f;
const float SCROLL_THRESHOLD = 10.f;
const float SCROLL_SPEED = 300.f;
const int KEY_D_CODE = 40;
const int KEY_WHITE_SPACE_CODE = 65;
+const char* HANDLE_IMAGE_FILE_NAME = TEST_RESOURCE_DIR "/insertpoint-icon.png";
+
static bool gTextChangedCallBackCalled;
static bool gInputStyleChangedCallbackCalled;
static Dali::Toolkit::TextEditor::InputStyle::Mask gInputStyleMask;
return tap;
}
+// Generate a PanGestureEvent to send to Core
+Integration::PanGestureEvent GeneratePan( Gesture::State state,
+ const Vector2& previousPosition,
+ const Vector2& currentPosition,
+ unsigned long timeDelta,
+ unsigned int numberOfTouches = 1u )
+{
+ Integration::PanGestureEvent pan(state);
+
+ pan.previousPosition = previousPosition;
+ pan.currentPosition = currentPosition;
+ pan.timeDelta = timeDelta;
+ pan.numberOfTouches = numberOfTouches;
+
+ return pan;
+}
+
// Generate a KeyEvent to send to Core.
Integration::KeyEvent GenerateKey( const std::string& keyName,
const std::string& keyString,
keyState );
}
+/**
+ * Helper to generate PanGestureEvent
+ *
+ * @param[in] application Application instance
+ * @param[in] state The Gesture State
+ * @param[in] pos The current position of touch.
+ */
+static void SendPan(ToolkitTestApplication& application, Gesture::State state, const Vector2& pos)
+{
+ static Vector2 last;
+
+ if( (state == Gesture::Started) ||
+ (state == Gesture::Possible) )
+ {
+ last.x = pos.x;
+ last.y = pos.y;
+ }
+
+ application.ProcessEvent( GeneratePan( state, last, pos, 16 ) );
+
+ last.x = pos.x;
+ last.y = pos.y;
+}
+
+/*
+ * Simulate time passed by.
+ *
+ * @note this will always process at least 1 frame (1/60 sec)
+ *
+ * @param application Test application instance
+ * @param duration Time to pass in milliseconds.
+ * @return The actual time passed in milliseconds
+ */
+static int Wait(ToolkitTestApplication& application, int duration = 0)
+{
+ int time = 0;
+
+ for(int i = 0; i <= ( duration / RENDER_FRAME_INTERVAL); i++)
+ {
+ application.SendNotification();
+ application.Render(RENDER_FRAME_INTERVAL);
+ time += RENDER_FRAME_INTERVAL;
+ }
+
+ return time;
+}
+
+Dali::Integration::Point GetPointDownInside( Vector2& pos )
+{
+ Dali::Integration::Point point;
+ point.SetState( PointState::DOWN );
+ point.SetScreenPosition( pos );
+ return point;
+}
+
+Dali::Integration::Point GetPointUpInside( Vector2& pos )
+{
+ Dali::Integration::Point point;
+ point.SetState( PointState::UP );
+ point.SetScreenPosition( pos );
+ return point;
+}
+
bool DaliTestCheckMaps( const Property::Map& fontStyleMapGet, const Property::Map& fontStyleMapSet )
{
if( fontStyleMapGet.Count() == fontStyleMapSet.Count() )
Renderer highlight = stencil.GetChildAt( 1u ).GetRendererAt( 0u );
DALI_TEST_CHECK( highlight );
+ // Double tap out of bounds
+ application.ProcessEvent( GenerateTap( Gesture::Possible, 2u, 1u, Vector2( 29.f, 25.0f ) ) );
+ application.ProcessEvent( GenerateTap( Gesture::Started, 2u, 1u, Vector2( 29.f, 25.0f ) ) );
+
+ // Render and notify
+ application.SendNotification();
+ application.Render();
+
+ // The stencil actor should have one actors: the renderer actor.
+ stencil = editor.GetChildAt( 0u );
+
+ // The stencil actor has a container with all the actors which contain the text renderers.
+ container = stencil.GetChildAt( 0u );
+ for( unsigned int index = 0; index < container.GetChildCount(); ++index )
+ {
+ Renderer renderer = container.GetChildAt( index ).GetRendererAt( 0u );
+ DALI_TEST_CHECK( renderer );
+ }
+
END_TEST;
}
END_TEST;
}
+
+int utcDaliTextEditorHandles(void)
+{
+ ToolkitTestApplication application;
+ tet_infoline(" utcDaliTextEditorHandles");
+
+ TextEditor editor = TextEditor::New();
+ DALI_TEST_CHECK( editor );
+
+ Stage::GetCurrent().Add( editor );
+
+ editor.SetProperty( TextEditor::Property::TEXT, "This is a long text for the size of the text-editor." );
+ editor.SetProperty( TextEditor::Property::POINT_SIZE, 10.f );
+ editor.SetProperty( TextEditor::Property::GRAB_HANDLE_IMAGE, HANDLE_IMAGE_FILE_NAME );
+ editor.SetProperty( DevelTextEditor::Property::SMOOTH_SCROLL, true );
+
+ editor.SetSize( 30.f, 500.f );
+ editor.SetParentOrigin( ParentOrigin::TOP_LEFT );
+ editor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
+
+ // Avoid a crash when core load gl resources.
+ application.GetGlAbstraction().SetCheckFramebufferStatusResult( GL_FRAMEBUFFER_COMPLETE );
+
+ // Render and notify
+ application.SendNotification();
+ application.Render();
+
+ // Tap first to get the focus.
+ application.ProcessEvent( GenerateTap( Gesture::Possible, 1u, 1u, Vector2( 3.f, 25.0f ) ) );
+ application.ProcessEvent( GenerateTap( Gesture::Started, 1u, 1u, Vector2( 3.f, 25.0f ) ) );
+
+ // Render and notify
+ application.SendNotification();
+ application.Render();
+
+ // Tap to create the grab handle.
+ application.ProcessEvent( GenerateTap( Gesture::Possible, 1u, 1u, Vector2( 3.f, 25.0f ) ) );
+ application.ProcessEvent( GenerateTap( Gesture::Started, 1u, 1u, Vector2( 3.f, 25.0f ) ) );
+
+ // Render and notify
+ application.SendNotification();
+ application.Render();
+
+ // Get the active layer where the text's decoration is added.
+ Actor activeLayer = editor.GetChildAt( 1u );
+
+ // Get the handle's actor.
+ Actor handle = activeLayer.GetChildAt( 1u );
+ handle.SetSize( 100.f, 100.f );
+
+ // Render and notify
+ application.SendNotification();
+ application.Render();
+
+ // Touch the grab handle to set it as pressed.
+ Vector2 touchPos( 10.0f, 50.0f );
+ Dali::Integration::TouchEvent event;
+ event = Dali::Integration::TouchEvent();
+ event.AddPoint( GetPointDownInside( touchPos ) );
+ application.ProcessEvent( event );
+
+ // Render and notify
+ application.SendNotification();
+ application.Render();
+
+ // drag grab handle right
+ SendPan(application, Gesture::Possible, touchPos);
+ SendPan(application, Gesture::Started, touchPos);
+ touchPos.x += 5.0f;
+ Wait(application, 100);
+
+ for(int i = 0;i<20;i++)
+ {
+ SendPan(application, Gesture::Continuing, touchPos);
+ touchPos.x += 5.0f;
+ Wait(application);
+ }
+
+ SendPan(application, Gesture::Finished, touchPos);
+ Wait(application);
+
+ // Release the grab handle.
+ event = Dali::Integration::TouchEvent();
+ event.AddPoint( GetPointUpInside( touchPos ) );
+ application.ProcessEvent( event );
+
+ // Render and notify
+ application.SendNotification();
+ application.Render();
+
+ END_TEST;
+}
+
Wait(application, 500);
+ // Long Press
+ application.ProcessEvent( GenerateLongPress( Gesture::Possible, 1u, Vector2( 1.f, 25.0f ) ) );
+ application.ProcessEvent( GenerateLongPress( Gesture::Started, 1u, Vector2( 1.f, 25.0f ) ) );
+
+ // Render and notify
+ application.SendNotification();
+ application.Render();
+
+ Wait(application, 500);
+
Stage stage = Stage::GetCurrent();
Layer layer = stage.GetRootLayer();
Actor actor = layer.FindChildByName("optionPaste");
application.ProcessEvent( event );
}
DALI_TEST_EQUALS( field.GetProperty<std::string>( TextEditor::Property::TEXT ), std::string("testTextFieldEvent"), TEST_LOCATION );
+
END_TEST;
}
// Disable the smooth handle panning.
mController->SetSmoothHandlePanEnabled( false );
+ mController->SetNoTextDoubleTapAction( Controller::NoTextTap::NO_ACTION );
+ mController->SetNoTextLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP );
+
// Forward input events to controller
EnableGestureDetection( static_cast<Gesture::Type>( Gesture::Tap | Gesture::Pan | Gesture::LongPress ) );
GetTapGestureDetector().SetMaximumTapsRequired( 2 );
/*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
: textBuffer( textBuffer ),
totalNumberOfCharacters( totalNumberOfCharacters ),
hitCharacter( hitCharacter ),
- foundIndex( 0u ),
+ foundIndex( 0 ),
isWhiteSpace( isWhiteSpace ),
isNewParagraph( isNewParagraph )
{}
{
LineIndex GetClosestLine( VisualModelPtr visualModel,
- float visualY )
+ float visualY,
+ bool& matchedLine )
{
float totalHeight = 0.f;
- LineIndex lineIndex = 0u;
+ LineIndex lineIndex = 0;
+ matchedLine = false;
+
+ if( visualY < 0.f )
+ {
+ return 0;
+ }
const Vector<LineRun>& lines = visualModel->mLines;
if( visualY < totalHeight )
{
+ matchedLine = true;
return lineIndex;
}
}
- if( lineIndex == 0u )
+ if( lineIndex == 0 )
{
return 0;
}
- return lineIndex-1;
+ return lineIndex - 1u;
}
float CalculateLineOffset( const Vector<LineRun>& lines,
LogicalModelPtr logicalModel,
MetricsPtr metrics,
float visualX,
- float visualY )
+ float visualY,
+ CharacterHitTest::Mode mode,
+ bool& matchedCharacter )
{
DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex, closest visualX %f visualY %f\n", visualX, visualY );
- CharacterIndex logicalIndex = 0u;
+ // Whether there is a hit on a glyph.
+ matchedCharacter = false;
+
+ CharacterIndex logicalIndex = 0;
const Length totalNumberOfGlyphs = visualModel->mGlyphs.Count();
const Length totalNumberOfLines = visualModel->mLines.Count();
return logicalIndex;
}
+ // Whether there is a hit on a line.
+ bool matchedLine = false;
+
// Find which line is closest.
const LineIndex lineIndex = Text::GetClosestLine( visualModel,
- visualY );
+ visualY,
+ matchedLine );
+
+ if( !matchedLine && ( CharacterHitTest::TAP == mode ) )
+ {
+ // Return the first or the last character if the touch point doesn't hit a line.
+ return ( visualY < 0.f ) ? 0 : logicalModel->mText.Count();
+ }
// Convert from text's coords to line's coords.
const LineRun& line = *( visualModel->mLines.Begin() + lineIndex );
// The character's direction buffer.
const CharacterDirection* const directionsBuffer = bidiLineFetched ? logicalModel->mCharacterDirections.Begin() : NULL;
- // Whether there is a hit on a glyph.
- bool matched = false;
+ // Whether the touch point if before the first glyph.
+ bool isBeforeFirstGlyph = false;
// Traverses glyphs in visual order. To do that use the visual to logical conversion table.
CharacterIndex visualIndex = startCharacter;
- Length numberOfVisualCharacters = 0u;
+ Length numberOfVisualCharacters = 0;
for( ; visualIndex < endCharacter; ++visualIndex )
{
// The character in logical order.
const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
++numberOfVisualCharacters;
- if( 0u != numberOfGlyphs )
+ if( 0 != numberOfGlyphs )
{
// Get the first character/glyph of the group of glyphs.
const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfVisualCharacters;
// Get the position of the first glyph.
const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
+ if( startCharacter == visualIndex )
+ {
+ const float glyphPosition = -glyphMetrics.xBearing + position.x;
+
+ if( visualX < glyphPosition )
+ {
+ isBeforeFirstGlyph = true;
+ break;
+ }
+ }
+
// Whether the glyph can be split, like Latin ligatures fi, ff or Arabic (ل + ا).
Length numberOfCharacters = *( charactersPerGlyphBuffer + firstLogicalGlyphIndex );
if( direction != LTR )
// number of glyphs in the table is found first.
// Jump the number of characters to the next glyph is needed.
- if( 0u == numberOfCharacters )
+ if( 0 == numberOfCharacters )
{
// TODO: This is a workaround to fix an issue with complex characters in the arabic
// script like i.e. رّ or الأَبْجَدِيَّة العَرَبِيَّة
// Find the number of characters.
for( GlyphIndex index = firstLogicalGlyphIndex + 1u;
- ( 0u == numberOfCharacters ) && ( index < totalNumberOfGlyphs ) ;
+ ( 0 == numberOfCharacters ) && ( index < totalNumberOfGlyphs );
++index )
{
numberOfCharacters = *( charactersPerGlyphBuffer + index );
const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
- CharacterIndex index = 0u;
+ CharacterIndex index = 0;
for( ; index < numberOfBlocks; ++index )
{
// Find the mid-point of the area containing the glyph
if( visualX < glyphCenter )
{
- matched = true;
+ matchedCharacter = true;
break;
}
}
- if( matched )
+ if( matchedCharacter )
{
// If the glyph is shaped from more than one character, it matches the character of the glyph.
visualIndex = firstVisualCharacterIndex + index;
break;
}
- numberOfVisualCharacters = 0u;
+ numberOfVisualCharacters = 0;
}
- }
+ } // for characters in visual order.
// The number of characters of the whole text.
const Length totalNumberOfCharacters = logicalModel->mText.Count();
// Return the logical position of the cursor in characters.
- if( !matched )
+ if( !matchedCharacter )
{
- // If no character is matched, then the last character (in visual order) of the line is used.
- visualIndex = endCharacter;
+ if( isBeforeFirstGlyph )
+ {
+ // If no character is matched, then the first character (in visual order) of the line is used.
+ visualIndex = startCharacter;
+ }
+ else
+ {
+ // If no character is matched, then the last character (in visual order) of the line is used.
+ visualIndex = endCharacter;
+ }
}
// Get the paragraph direction.
isCurrentRightToLeft = *( directionsBuffer + index );
}
- Length numberOfGlyphAdvance = ( isFirstPositionOfLine ? 0u : 1u ) + characterIndex - firstIndex;
+ Length numberOfGlyphAdvance = ( isFirstPositionOfLine ? 0 : 1u ) + characterIndex - firstIndex;
if( isCurrentRightToLeft )
{
numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
float visualX,
float visualY,
CharacterIndex& startIndex,
- CharacterIndex& endIndex )
+ CharacterIndex& endIndex,
+ CharacterIndex& noTextHitIndex )
{
-
/*
Hit character Select
|-------------------------------------------------------|------------------------------------------|
| On a new paragraph character | The word or group of white spaces before |
|-------------------------------------------------------|------------------------------------------|
*/
+ const Length totalNumberOfCharacters = logicalModel->mText.Count();
+ startIndex = 0;
+ endIndex = 0;
+ noTextHitIndex = 0;
+ if( 0 == totalNumberOfCharacters )
+ {
+ // Nothing to do if the model is empty.
+ return false;
+ }
+
+ bool matchedCharacter = false;
CharacterIndex hitCharacter = Text::GetClosestCursorIndex( visualModel,
logicalModel,
metrics,
visualX,
- visualY );
-
- const Length totalNumberOfCharacters = logicalModel->mText.Count();
-
- DALI_ASSERT_DEBUG( ( hitCharacter <= totalNumberOfCharacters ) && "GetClosestCursorIndex returned out of bounds index" );
+ visualY,
+ CharacterHitTest::TAP,
+ matchedCharacter );
- if( 0u == totalNumberOfCharacters )
+ if( !matchedCharacter )
{
- // Nothing to do if the model is empty.
- return false;
+ noTextHitIndex = hitCharacter;
}
+ DALI_ASSERT_DEBUG( ( hitCharacter <= totalNumberOfCharacters ) && "GetClosestCursorIndex returned out of bounds index" );
+
if( hitCharacter >= totalNumberOfCharacters )
{
// Closest hit character is the last character.
{
// Find the first character before the hit one which is not a new paragraph character.
- if( hitCharacter > 0u )
+ if( hitCharacter > 0 )
{
endIndex = hitCharacter - 1u;
for( ; endIndex > 0; --endIndex )
{
// Select the word before or after the white space
- if( 0u == hitCharacter )
+ if( 0 == hitCharacter )
{
data.isWhiteSpace = false;
FindEndOfWord( data );
endIndex = data.foundIndex;
}
- else if( hitCharacter > 0u )
+ else if( hitCharacter > 0 )
{
// Find the start of the word.
data.hitCharacter = hitCharacter - 1u;
}
}
- return true;
+ return matchedCharacter;
}
} // namespace Text
-#ifndef __DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H__
-#define __DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H__
+#ifndef DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H
+#define DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H
/*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
namespace Text
{
+struct CharacterHitTest
+{
+ /**
+ * @brief Enumeration of the types of hit test.
+ */
+ enum Mode
+ {
+ TAP, ///< Retrieves the first or last character of the line if the touch point is outside of the boundaries of the text.
+ SCROLL ///< Retrieves the character above or below to the touch point if it's outside of the boundaries of the text.
+ };
+};
+
struct CursorInfo
{
CursorInfo()
*
* @param[in] visualModel The visual model.
* @param[in] visualY The touch point 'y' in text's coords.
+ * @param[out] matchedLine Whether the touch point actually hits a line.
*
* @return A line index.
*/
LineIndex GetClosestLine( VisualModelPtr visualModel,
- float visualY );
+ float visualY,
+ bool& matchedLine );
/**
* @brief Calculates the vertical line's offset for a given line.
/**
* @brief Retrieves the cursor's logical position for a given touch point x,y
*
+ * There are two types of hit test: CharacterHitTest::TAP retrieves the first or
+ * last character of a line if the touch point is outside the boundaries of the
+ * text, CharacterHitTest::SCROLL retrieves the character above or below to the
+ * touch point if it's outside the boundaries of the text.
+ *
* @param[in] visualModel The visual model.
* @param[in] logicalModel The logical model.
* @param[in] metrics A wrapper around FontClient used to get metrics.
* @param[in] visualX The touch point 'x' in text's coords.
* @param[in] visualY The touch point 'y' in text's coords.
+ * @param[in] mode The type of hit test.
+ * @param[out] matchedCharacter Whether the touch point actually hits a character.
*
* @return The logical cursor position (in characters). 0 is just before the first character, a value equal to the number of characters is just after the last character.
*/
LogicalModelPtr logicalModel,
MetricsPtr metrics,
float visualX,
- float visualY );
-
+ float visualY,
+ CharacterHitTest::Mode mode,
+ bool& matchedCharacter );
/**
* @brief Calculates the cursor's position for a given character index in the logical order.
* @param[in] visualY The touch point 'y' in text's coords.
* @param[out] startIndex Index to the first character of the selected word.
* @param[out] endIndex Index to the last character of the selected word.
+ * @param[out] noTextHitIndex Index to the nearest character when there is no hit.
*
- * @return @e true if the indices are found.
+ * @return @e true if the touch point hits a character.
*/
bool FindSelectionIndices( VisualModelPtr visualModel,
LogicalModelPtr logicalModel,
float visualX,
float visualY,
CharacterIndex& startIndex,
- CharacterIndex& endIndex );
+ CharacterIndex& endIndex,
+ CharacterIndex& noTextHitIndex );
} // namespace Text
} // namespace Dali
-#endif // __DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H__
+#endif // DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H
mPreEditStartPosition( 0u ),
mPreEditLength( 0u ),
mCursorHookPositionX( 0.f ),
+ mDoubleTapAction( Controller::NoTextTap::NO_ACTION ),
+ mLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP ),
mIsShowingPlaceholderText( false ),
mPreEditFlag( false ),
mDecoratorUpdated( false ),
mUpdateLeftSelectionPosition( false ),
mUpdateRightSelectionPosition( false ),
mIsLeftHandleSelected( false ),
+ mIsRightHandleSelected( false ),
mUpdateHighlightBox( false ),
mScrollAfterUpdatePosition( false ),
mScrollAfterDelete( false ),
if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) )
{
- CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
+ if( mEventData->mIsLeftHandleSelected && mEventData->mIsRightHandleSelected )
+ {
+ CursorInfo& infoLeft = leftHandleInfo;
+
+ const Vector2 currentCursorPositionLeft( infoLeft.primaryPosition.x, infoLeft.lineOffset );
+ ScrollToMakePositionVisible( currentCursorPositionLeft, infoLeft.lineHeight );
+
+ CursorInfo& infoRight = rightHandleInfo;
- const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset );
- ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight );
+ const Vector2 currentCursorPositionRight( infoRight.primaryPosition.x, infoRight.lineOffset );
+ ScrollToMakePositionVisible( currentCursorPositionRight, infoRight.lineHeight );
+ }
+ else
+ {
+ CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
+
+ const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset );
+ ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight );
+ }
}
}
mEventData->mUpdateLeftSelectionPosition = false;
mEventData->mUpdateRightSelectionPosition = false;
mEventData->mUpdateHighlightBox = false;
+ mEventData->mIsLeftHandleSelected = false;
+ mEventData->mIsRightHandleSelected = false;
}
mEventData->mScrollAfterUpdatePosition = false;
const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
// Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
+ bool matchedCharacter = false;
mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
mModel->mLogicalModel,
mMetrics,
mEventData->mCursorHookPositionX,
- hitPointY );
+ hitPointY,
+ CharacterHitTest::TAP,
+ matchedCharacter );
}
- else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
+ else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
{
// Get first the line index of the current cursor position index.
CharacterIndex characterIndex = 0u;
const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
// Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
+ bool matchedCharacter = false;
mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
mModel->mLogicalModel,
mMetrics,
mEventData->mCursorHookPositionX,
- hitPointY );
+ hitPointY,
+ CharacterHitTest::TAP,
+ matchedCharacter );
}
}
// Keep the tap 'x' position. Used to move the cursor.
mEventData->mCursorHookPositionX = xPosition;
+ // Whether to touch point hits on a glyph.
+ bool matchedCharacter = false;
mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
mModel->mLogicalModel,
mMetrics,
xPosition,
- yPosition );
+ yPosition,
+ CharacterHitTest::TAP,
+ matchedCharacter );
// When the cursor position is changing, delay cursor blinking
mEventData->mDecorator->DelayCursorBlink();
mEventData->mImfManager.NotifyCursorPosition();
}
}
+ else if( 2u == tapCount )
+ {
+ if( mEventData->mSelectionEnabled )
+ {
+ // Convert from control's coords to text's coords.
+ const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
+ const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
+
+ // Calculates the logical position from the x,y coords.
+ RepositionSelectionHandles( xPosition,
+ yPosition,
+ mEventData->mDoubleTapAction );
+ }
+ }
}
}
{
DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
- if( EventData::EDITING == mEventData->mState )
+ if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) )
{
- ChangeState ( EventData::EDITING_WITH_POPUP );
+ ChangeState( EventData::EDITING_WITH_POPUP );
mEventData->mDecoratorUpdated = true;
mEventData->mUpdateInputStyle = true;
}
+ else
+ {
+ if( mEventData->mSelectionEnabled )
+ {
+ // Convert from control's coords to text's coords.
+ const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
+ const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
+
+ // Calculates the logical position from the x,y coords.
+ RepositionSelectionHandles( xPosition,
+ yPosition,
+ mEventData->mLongPressAction );
+ }
+ }
}
void Controller::Impl::OnHandleEvent( const Event& event )
const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
// Need to calculate the handle's new position.
+ bool matchedCharacter = false;
const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
mModel->mLogicalModel,
mMetrics,
xPosition,
- yPosition );
+ yPosition,
+ CharacterHitTest::SCROLL,
+ matchedCharacter );
if( Event::GRAB_HANDLE_EVENT == event.type )
{
// Will define the order to scroll the text to match the handle position.
mEventData->mIsLeftHandleSelected = true;
+ mEventData->mIsRightHandleSelected = false;
}
else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
{
// Will define the order to scroll the text to match the handle position.
mEventData->mIsLeftHandleSelected = false;
+ mEventData->mIsRightHandleSelected = true;
}
} // end ( HANDLE_PRESSED == state )
else if( ( HANDLE_RELEASED == state ) ||
const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
+ bool matchedCharacter = false;
handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
mModel->mLogicalModel,
mMetrics,
xPosition,
- yPosition );
+ yPosition,
+ CharacterHitTest::SCROLL,
+ matchedCharacter );
}
if( Event::GRAB_HANDLE_EVENT == event.type )
// Get the new handle position.
// The grab handle's position is in decorator's coords. Need to transforms to text's coords.
+ bool matchedCharacter = false;
const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
mModel->mLogicalModel,
mMetrics,
position.x - mModel->mScrollPosition.x,
- position.y - mModel->mScrollPosition.y );
+ position.y - mModel->mScrollPosition.y,
+ CharacterHitTest::SCROLL,
+ matchedCharacter );
if( mEventData->mPrimaryCursorPosition != handlePosition )
{
// Get the new handle position.
// The selection handle's position is in decorator's coords. Need to transform to text's coords.
+ bool matchedCharacter = false;
const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
mModel->mLogicalModel,
mMetrics,
position.x - mModel->mScrollPosition.x,
- position.y - mModel->mScrollPosition.y );
+ position.y - mModel->mScrollPosition.y,
+ CharacterHitTest::SCROLL,
+ matchedCharacter );
if( leftSelectionHandleEvent )
{
// Calculates the logical position from the x,y coords.
RepositionSelectionHandles( xPosition,
- yPosition );
+ yPosition,
+ Controller::NoTextTap::HIGHLIGHT );
}
}
mEventData->mDecoratorUpdated = true;
}
-void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
+void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action )
{
if( NULL == mEventData )
{
// Find which word was selected
CharacterIndex selectionStart( 0 );
CharacterIndex selectionEnd( 0 );
- const bool indicesFound = FindSelectionIndices( mModel->mVisualModel,
+ CharacterIndex noTextHitIndex( 0 );
+ const bool characterHit = FindSelectionIndices( mModel->mVisualModel,
mModel->mLogicalModel,
mMetrics,
visualX,
visualY,
selectionStart,
- selectionEnd );
+ selectionEnd,
+ noTextHitIndex );
DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
- if( indicesFound )
+ if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) )
{
ChangeState( EventData::SELECTING );
mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition );
}
- else
+ else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action )
{
// Nothing to select. i.e. a white space, out of bounds
- ChangeState( EventData::EDITING );
+ ChangeState( EventData::EDITING_WITH_POPUP );
- mEventData->mPrimaryCursorPosition = selectionEnd;
+ mEventData->mPrimaryCursorPosition = noTextHitIndex;
+
+ mEventData->mUpdateCursorPosition = true;
+ mEventData->mUpdateGrabHandlePosition = true;
+ mEventData->mScrollAfterUpdatePosition = true;
+ mEventData->mUpdateInputStyle = true;
+ }
+ else if( Controller::NoTextTap::NO_ACTION == action )
+ {
+ // Nothing to select. i.e. a white space, out of bounds
+ mEventData->mPrimaryCursorPosition = noTextHitIndex;
mEventData->mUpdateCursorPosition = true;
mEventData->mUpdateGrabHandlePosition = true;
#define DALI_TOOLKIT_TEXT_CONTROLLER_IMPL_H
/*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
float mCursorHookPositionX; ///< Used to move the cursor with the keys or when scrolling the text vertically with the handles.
+ Controller::NoTextTap::Action mDoubleTapAction; ///< Action to be done when there is a double tap on top of 'no text'
+ Controller::NoTextTap::Action mLongPressAction; ///< Action to be done when there is a long press on top of 'no text'
+
bool mIsShowingPlaceholderText : 1; ///< True if the place-holder text is being displayed.
bool mPreEditFlag : 1; ///< True if the model contains text in pre-edit state.
bool mDecoratorUpdated : 1; ///< True if the decorator was updated during event processing.
bool mUpdateLeftSelectionPosition : 1; ///< True if the visual position of the left selection handle must be recalculated.
bool mUpdateRightSelectionPosition : 1; ///< True if the visual position of the right selection handle must be recalculated.
bool mIsLeftHandleSelected : 1; ///< Whether is the left handle the one which is selected.
+ bool mIsRightHandleSelected : 1; ///< Whether is the right handle the one which is selected.
bool mUpdateHighlightBox : 1; ///< True if the text selection high light box must be updated.
bool mScrollAfterUpdatePosition : 1; ///< Whether to scroll after the cursor position is updated.
bool mScrollAfterDelete : 1; ///< Whether to scroll after delete characters.
void RequestGetTextFromClipboard();
void RepositionSelectionHandles();
- void RepositionSelectionHandles( float visualX, float visualY );
+ void RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action );
void SetPopupButtons();
/*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
return false;
}
+void Controller::SetNoTextDoubleTapAction( NoTextTap::Action action )
+{
+ if( NULL != mImpl->mEventData )
+ {
+ mImpl->mEventData->mDoubleTapAction = action;
+ }
+}
+
+Controller::NoTextTap::Action Controller::GetNoTextDoubleTapAction() const
+{
+ NoTextTap::Action action = NoTextTap::NO_ACTION;
+
+ if( NULL != mImpl->mEventData )
+ {
+ action = mImpl->mEventData->mDoubleTapAction;
+ }
+
+ return action;
+}
+
+void Controller::SetNoTextLongPressAction( NoTextTap::Action action )
+{
+ if( NULL != mImpl->mEventData )
+ {
+ mImpl->mEventData->mLongPressAction = action;
+ }
+}
+
+Controller::NoTextTap::Action Controller::GetNoTextLongPressAction() const
+{
+ NoTextTap::Action action = NoTextTap::NO_ACTION;
+
+ if( NULL != mImpl->mEventData )
+ {
+ action = mImpl->mEventData->mLongPressAction;
+ }
+
+ return action;
+}
+
// public : Queries & retrieves.
Layout::Engine& Controller::GetLayoutEngine()
if( mImpl->mEventData->mSelectionEnabled &&
mImpl->IsShowingRealText() )
{
- SelectEvent( x, y, false );
+ relayoutNeeded = true;
+ mImpl->mEventData->mIsLeftHandleSelected = true;
+ mImpl->mEventData->mIsRightHandleSelected = true;
}
}
+
// Handles & cursors must be repositioned after Relayout() i.e. after the Model has been updated
if( relayoutNeeded )
{
if( ( state == Gesture::Started ) &&
( NULL != mImpl->mEventData ) )
{
- if( !mImpl->IsShowingRealText() )
+ // The 1st long-press on inactive text-field is treated as tap
+ if( EventData::INACTIVE == mImpl->mEventData->mState )
+ {
+ mImpl->ChangeState( EventData::EDITING );
+
+ Event event( Event::TAP_EVENT );
+ event.p1.mUint = 1;
+ event.p2.mFloat = x;
+ event.p3.mFloat = y;
+ mImpl->mEventData->mEventQueue.push_back( event );
+
+ mImpl->RequestRelayout();
+ }
+ else if( !mImpl->IsShowingRealText() )
{
Event event( Event::LONG_PRESS_EVENT );
event.p1.mInt = state;
+ event.p2.mFloat = x;
+ event.p3.mFloat = y;
mImpl->mEventData->mEventQueue.push_back( event );
mImpl->RequestRelayout();
}
- else
+ else if( !mImpl->IsClipboardVisible() )
{
- // The 1st long-press on inactive text-field is treated as tap
- if( EventData::INACTIVE == mImpl->mEventData->mState )
- {
- mImpl->ChangeState( EventData::EDITING );
+ // Reset the imf manager to commit the pre-edit before selecting the text.
+ mImpl->ResetImfManager();
- Event event( Event::TAP_EVENT );
- event.p1.mUint = 1;
- event.p2.mFloat = x;
- event.p3.mFloat = y;
- mImpl->mEventData->mEventQueue.push_back( event );
-
- mImpl->RequestRelayout();
- }
- else
- {
- // Reset the imf manger to commit the pre-edit before selecting the text.
- mImpl->ResetImfManager();
+ Event event( Event::LONG_PRESS_EVENT );
+ event.p1.mInt = state;
+ event.p2.mFloat = x;
+ event.p3.mFloat = y;
+ mImpl->mEventData->mEventQueue.push_back( event );
+ mImpl->RequestRelayout();
- SelectEvent( x, y, false );
- }
+ mImpl->mEventData->mIsLeftHandleSelected = true;
+ mImpl->mEventData->mIsRightHandleSelected = true;
}
}
}
}
mImpl->mEventData->mCheckScrollAmount = true;
+ mImpl->mEventData->mIsLeftHandleSelected = true;
+ mImpl->mEventData->mIsRightHandleSelected = true;
mImpl->RequestRelayout();
}
}
#define DALI_TOOLKIT_TEXT_CONTROLLER_H
/*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
PLACEHOLDER_TYPE_INACTIVE,
};
+ struct NoTextTap
+ {
+ enum Action
+ {
+ NO_ACTION, ///< Does no action if there is a tap on top of an area with no text.
+ HIGHLIGHT, ///< Highlights the nearest text (at the beginning or end of the text) and shows the text's selection popup.
+ SHOW_SELECTION_POPUP ///< Shows the text's selection popup.
+ };
+ };
+
public: // Constructor.
/**
*/
bool IsInputModePassword();
+ /**
+ * @brief Sets the action when there is a double tap event on top of a text area with no text.
+ *
+ * @param[in] action The action to do.
+ */
+ void SetNoTextDoubleTapAction( NoTextTap::Action action );
+
+ /**
+ * @brief Retrieves the action when there is a double tap event on top of a text area with no text.
+ *
+ * @return The action to do.
+ */
+ NoTextTap::Action GetNoTextDoubleTapAction() const;
+
+ /**
+ * @briefSets the action when there is a long press event on top of a text area with no text.
+ *
+ * @param[in] action The action to do.
+ */
+ void SetNoTextLongPressAction( NoTextTap::Action action );
+
+ /**
+ * @brief Retrieves the action when there is a long press event on top of a text area with no text.
+ *
+ * @return The action to do.
+ */
+ NoTextTap::Action GetNoTextLongPressAction() const;
+
public: // Update.
/**