/*
- * Copyright (c) 2015 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-toolkit/internal/text/multi-language-support.h>
#include <dali-toolkit/internal/text/segmentation.h>
#include <dali-toolkit/internal/text/shaper.h>
+#include <dali-toolkit/internal/text/text-control-interface.h>
#include <dali-toolkit/internal/text/text-run-container.h>
namespace
mPlaceholderTextInactive(),
mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
mEventQueue(),
+ mInputStyleChangedQueue(),
+ mPreviousState( INACTIVE ),
mState( INACTIVE ),
mPrimaryCursorPosition( 0u ),
mLeftSelectionPosition( 0u ),
mPreEditStartPosition( 0u ),
mPreEditLength( 0u ),
mCursorHookPositionX( 0.f ),
+ mDoubleTapAction( Controller::NoTextTap::NO_ACTION ),
+ mLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP ),
mIsShowingPlaceholderText( false ),
mPreEditFlag( false ),
mDecoratorUpdated( false ),
mGrabHandleEnabled( true ),
mGrabHandlePopupEnabled( true ),
mSelectionEnabled( true ),
+ mUpdateCursorHookPosition( false ),
mUpdateCursorPosition( false ),
mUpdateGrabHandlePosition( 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 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 );
+ 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;
if( mEventData->mUpdateInputStyle )
{
+ // Keep a copy of the current input style.
+ InputStyle currentInputStyle;
+ currentInputStyle.Copy( mEventData->mInputStyle );
+
// Set the default style first.
RetrieveDefaultInputStyle( mEventData->mInputStyle );
// Retrieve the style from the style runs stored in the logical model.
mLogicalModel->RetrieveStyle( styleIndex, mEventData->mInputStyle );
+ // Compare if the input style has changed.
+ const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
+
+ if( hasInputStyleChanged )
+ {
+ const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
+ // Queue the input style changed signal.
+ mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
+ }
+
mEventData->mUpdateInputStyle = false;
}
}
}
+void Controller::Impl::NotifyImfMultiLineStatus()
+{
+ if ( mEventData )
+ {
+ LayoutEngine::Layout layout = mLayoutEngine.GetLayout();
+ mEventData->mImfManager.NotifyTextInputMultiLine( layout == LayoutEngine::MULTI_LINE_BOX );
+ }
+}
+
CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
{
CharacterIndex cursorPosition = 0u;
// Validate the fonts set through the mark-up string.
Vector<FontDescriptionRun>& fontDescriptionRuns = mLogicalModel->mFontDescriptionRuns;
- // Get the default font id.
- const FontId defaultFontId = ( NULL == mFontDefaults ) ? 0u : mFontDefaults->GetFontId( mFontClient );
+ // Get the default font's description.
+ TextAbstraction::FontDescription defaultFontDescription;
+ TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
+ if( NULL != mFontDefaults )
+ {
+ defaultFontDescription = mFontDefaults->mFontDescription;
+ defaultPointSize = mFontDefaults->mDefaultPointSize * 64u;
+ }
// Validates the fonts. If there is a character with no assigned font it sets a default one.
// After this call, fonts are validated.
multilanguageSupport.ValidateFonts( utf32Characters,
scripts,
fontDescriptionRuns,
- defaultFontId,
+ defaultFontDescription,
+ defaultPointSize,
startIndex,
requestedNumberOfCharacters,
validFonts );
inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
inputStyle.size = 0.f;
- inputStyle.familyDefined = false;
- inputStyle.weightDefined = false;
- inputStyle.widthDefined = false;
- inputStyle.slantDefined = false;
- inputStyle.sizeDefined = false;
+ inputStyle.lineSpacing = 0.f;
+
+ inputStyle.underlineProperties.clear();
+ inputStyle.shadowProperties.clear();
+ inputStyle.embossProperties.clear();
+ inputStyle.outlineProperties.clear();
+
+ inputStyle.isFamilyDefined = false;
+ inputStyle.isWeightDefined = false;
+ inputStyle.isWidthDefined = false;
+ inputStyle.isSlantDefined = false;
+ inputStyle.isSizeDefined = false;
+
+ inputStyle.isLineSpacingDefined = false;
+
+ inputStyle.isUnderlineDefined = false;
+ inputStyle.isShadowDefined = false;
+ inputStyle.isEmbossDefined = false;
+ inputStyle.isOutlineDefined = false;
// Sets the default font's family name, weight, width, slant and size.
if( mFontDefaults )
if( mFontDefaults->familyDefined )
{
inputStyle.familyName = mFontDefaults->mFontDescription.family;
- inputStyle.familyDefined = true;
+ inputStyle.isFamilyDefined = true;
}
if( mFontDefaults->weightDefined )
{
inputStyle.weight = mFontDefaults->mFontDescription.weight;
- inputStyle.weightDefined = true;
+ inputStyle.isWeightDefined = true;
}
if( mFontDefaults->widthDefined )
{
inputStyle.width = mFontDefaults->mFontDescription.width;
- inputStyle.widthDefined = true;
+ inputStyle.isWidthDefined = true;
}
if( mFontDefaults->slantDefined )
{
inputStyle.slant = mFontDefaults->mFontDescription.slant;
- inputStyle.slantDefined = true;
+ inputStyle.isSlantDefined = true;
}
if( mFontDefaults->sizeDefined )
{
inputStyle.size = mFontDefaults->mDefaultPointSize;
- inputStyle.sizeDefined = true;
+ inputStyle.isSizeDefined = true;
}
}
}
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( mVisualModel,
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( mVisualModel,
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( mVisualModel,
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 - mScrollPosition.x;
+ const float yPosition = event.p3.mFloat - 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 - mScrollPosition.x;
+ const float yPosition = event.p3.mFloat - mScrollPosition.y;
+
+ // Calculates the logical position from the x,y coords.
+ RepositionSelectionHandles( xPosition,
+ yPosition,
+ mEventData->mLongPressAction );
+ }
}
}
const float yPosition = event.p3.mFloat - mScrollPosition.y;
// Need to calculate the handle's new position.
+ bool matchedCharacter = false;
const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mVisualModel,
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 - mScrollPosition.x;
const float yPosition = event.p3.mFloat - mScrollPosition.y;
+ bool matchedCharacter = false;
handlePosition = Text::GetClosestCursorIndex( mVisualModel,
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( mVisualModel,
mLogicalModel,
mMetrics,
position.x - mScrollPosition.x,
- position.y - mScrollPosition.y );
+ position.y - 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( mVisualModel,
mLogicalModel,
mMetrics,
position.x - mScrollPosition.x,
- position.y - mScrollPosition.y );
+ position.y - mScrollPosition.y,
+ CharacterHitTest::SCROLL,
+ matchedCharacter );
if( leftSelectionHandleEvent )
{
// Calculates the logical position from the x,y coords.
RepositionSelectionHandles( xPosition,
- yPosition );
+ yPosition,
+ Controller::NoTextTap::HIGHLIGHT );
}
}
if( deleteAfterRetrieval ) // Only delete text if copied successfully
{
+ // Keep a copy of the current input style.
+ InputStyle currentInputStyle;
+ currentInputStyle.Copy( mEventData->mInputStyle );
+
// Set as input style the style of the first deleted character.
mLogicalModel->RetrieveStyle( startOfSelectedText, mEventData->mInputStyle );
+ // Compare if the input style has changed.
+ const bool hasInputStyleChanged = !currentInputStyle.Equal( mEventData->mInputStyle );
+
+ if( hasInputStyleChanged )
+ {
+ const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask( mEventData->mInputStyle );
+ // Queue the input style changed signal.
+ mEventData->mInputStyleChangedQueue.PushBack( styleChangedMask );
+ }
+
mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast<int>( lengthOfSelectedText ) );
// Mark the paragraphs to be updated.
- mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
- mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
+ if( LayoutEngine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
+ {
+ mTextUpdateInfo.mCharacterIndex = 0;
+ mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
+ mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
+ mTextUpdateInfo.mClearAll = true;
+ }
+ else
+ {
+ mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
+ mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
+ }
// Delete text between handles
Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
ChangeState( EventData::EDITING );
}
-void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retrievedString )
+void Controller::Impl::RequestGetTextFromClipboard()
{
if ( mClipboard )
{
- retrievedString = mClipboard.GetItem( itemIndex );
+ mClipboard.RequestItem();
}
}
// Whether to retrieve the next line.
if( index == lastGlyphOfLine )
{
- // Retrieve the next line.
- ++lineRun;
-
- // Get the last glyph of the new line.
- lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
-
++lineIndex;
if( lineIndex < firstLineIndex + numberOfLines )
{
+ // Retrieve the next line.
+ ++lineRun;
+
+ // Get the last glyph of the new line.
+ lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
+
// Keep the offset and height of the current selection box.
const float currentLineOffset = selectionBoxInfo->lineOffset;
const float currentLineHeight = selectionBoxInfo->lineHeight;
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 )
{
return;
}
- if( IsShowingPlaceholderText() )
- {
- // Nothing to do if there is the place-holder text.
- return;
- }
-
const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
const Length numberOfLines = mVisualModel->mLines.Count();
if( ( 0 == numberOfGlyphs ) ||
// Find which word was selected
CharacterIndex selectionStart( 0 );
CharacterIndex selectionEnd( 0 );
- const bool indicesFound = FindSelectionIndices( mVisualModel,
+ CharacterIndex noTextHitIndex( 0 );
+ const bool characterHit = FindSelectionIndices( mVisualModel,
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->mUpdateRightSelectionPosition = true;
mEventData->mUpdateHighlightBox = true;
+ // It may happen an IMF commit event arrives before the selection event
+ // if the IMF manager is in pre-edit state. The commit event will set the
+ // mEventData->mUpdateCursorPosition flag to true. If it's not set back
+ // to false, the highlight box won't be updated.
+ mEventData->mUpdateCursorPosition = false;
+
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 = noTextHitIndex;
- mEventData->mPrimaryCursorPosition = selectionEnd;
+ 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;
mEventData->mDecorator->SetHighlightActive( false );
mEventData->mDecorator->SetPopupActive( false );
mEventData->mDecoratorUpdated = true;
- HideClipboard();
break;
}
case EventData::INTERRUPTED:
mEventData->mDecorator->SetHighlightActive( false );
mEventData->mDecorator->SetPopupActive( false );
mEventData->mDecoratorUpdated = true;
- HideClipboard();
break;
}
case EventData::SELECTING:
mEventData->mDecorator->SetPopupActive( false );
}
mEventData->mDecoratorUpdated = true;
- HideClipboard();
break;
}
case EventData::EDITING_WITH_POPUP:
SetPopupButtons();
mEventData->mDecorator->SetPopupActive( true );
}
- HideClipboard();
mEventData->mDecoratorUpdated = true;
break;
}
mEventData->mDecorator->SetPopupActive( false );
}
mEventData->mDecoratorUpdated = true;
- HideClipboard();
break;
}
case EventData::SELECTION_HANDLE_PANNING:
SetPopupButtons();
mEventData->mDecorator->SetPopupActive( true );
}
- HideClipboard();
mEventData->mDecoratorUpdated = true;
break;
}
return;
}
- Text::GetCursorPosition( mVisualModel,
- mLogicalModel,
- mMetrics,
- logical,
+ const bool isMultiLine = ( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() );
+ GetCursorPositionParameters parameters;
+ parameters.visualModel = mVisualModel;
+ parameters.logicalModel = mLogicalModel;
+ parameters.metrics = mMetrics;
+ parameters.logical = logical;
+ parameters.isMultiline = isMultiLine;
+
+ Text::GetCursorPosition( parameters,
cursorInfo );
- if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
+ if( isMultiLine )
{
// If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
void Controller::Impl::ClampHorizontalScroll( const Vector2& layoutSize )
{
- // Clamp between -space & 0.
+ // Clamp between -space & -alignment offset.
if( layoutSize.width > mVisualModel->mControlSize.width )
{
- const float space = ( layoutSize.width - mVisualModel->mControlSize.width );
+ const float space = ( layoutSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset;
mScrollPosition.x = ( mScrollPosition.x < -space ) ? -space : mScrollPosition.x;
- mScrollPosition.x = ( mScrollPosition.x > 0.f ) ? 0.f : mScrollPosition.x;
+ mScrollPosition.x = ( mScrollPosition.x > -mAlignmentOffset ) ? -mAlignmentOffset : mScrollPosition.x;
mEventData->mDecoratorUpdated = true;
}
void Controller::Impl::ClampVerticalScroll( const Vector2& layoutSize )
{
+ if( LayoutEngine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() )
+ {
+ // Nothing to do if the text is single line.
+ return;
+ }
+
// Clamp between -space & 0.
if( layoutSize.height > mVisualModel->mControlSize.height )
{
mScrollPosition.x = mVisualModel->mControlSize.width - positionEndX;
}
- if( decoratorPositionBeginY < 0.f )
- {
- mScrollPosition.y = -position.y;
- }
- else if( decoratorPositionEndY > mVisualModel->mControlSize.height )
+ if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
{
- mScrollPosition.y = mVisualModel->mControlSize.height - positionEndY;
+ if( decoratorPositionBeginY < 0.f )
+ {
+ mScrollPosition.y = -position.y;
+ }
+ else if( decoratorPositionEndY > mVisualModel->mControlSize.height )
+ {
+ mScrollPosition.y = mVisualModel->mControlSize.height - positionEndY;
+ }
}
}
// Calculate the offset to match the cursor position before the character was deleted.
mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
- mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset;
-
ClampHorizontalScroll( mVisualModel->GetLayoutSize() );
- ClampVerticalScroll( mVisualModel->GetLayoutSize() );
+
+ if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() )
+ {
+ mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset;
+ ClampVerticalScroll( mVisualModel->GetLayoutSize() );
+ }
// Makes the new cursor position visible if needed.
ScrollToMakePositionVisible( cursorInfo.primaryPosition, cursorInfo.lineHeight );
void Controller::Impl::RequestRelayout()
{
- mControlInterface.RequestTextRelayout();
+ if( NULL != mControlInterface )
+ {
+ mControlInterface->RequestTextRelayout();
+ }
}
} // namespace Text