Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(809)

Unified Diff: Source/core/editing/GranularityStrategy.cpp

Issue 1123563003: Improving direction-based selection strategy. (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Removing anonymous namespace and marking functions static instead. Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « Source/core/editing/GranularityStrategy.h ('k') | Source/core/editing/GranularityStrategyTest.cpp » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: Source/core/editing/GranularityStrategy.cpp
diff --git a/Source/core/editing/GranularityStrategy.cpp b/Source/core/editing/GranularityStrategy.cpp
index 5a5fd9ee30330abf12efdd949c68fff152dc0636..3ab12fc44c013a830b42a50861f6cef9e76aafbe 100644
--- a/Source/core/editing/GranularityStrategy.cpp
+++ b/Source/core/editing/GranularityStrategy.cpp
@@ -5,10 +5,53 @@
#include "config.h"
#include "core/editing/GranularityStrategy.h"
+#include "core/editing/FrameSelection.h"
#include "core/editing/htmlediting.h"
namespace blink {
+enum class BoundAdjust {CurrentPosIfOnBound, NextBoundIfOnBound};
+enum class SearchDirection {SearchBackwards, SearchForward};
+
+// We use the bottom-left corner of the caret rect to represent the
+// location of a VisiblePosition. This way locations corresponding to
+// VisiblePositions on the same line will all have the same y coordinate
+// unless the text is transformed.
+static IntPoint positionLocation(const VisiblePosition& vp)
+{
+ return vp.absoluteCaretBounds().minXMaxYCorner();
+}
+
+// Order is specified using the same contract as comparePositions.
+static bool arePositionsInSpecifiedOrder(
+ const VisiblePosition& vp1,
+ const VisiblePosition& vp2,
+ int specifiedOrder)
+{
+ int positionOrder = comparePositions(vp1, vp2);
+ if (specifiedOrder == 0)
+ return positionOrder == 0;
+ return specifiedOrder > 0 ? positionOrder > 0 : positionOrder < 0;
+}
+
+// Returns the next word boundary starting from |pos|. |direction| specifies
+// the direction in which to search for the next bound. nextIfOnBound
+// controls whether |pos| or the next boundary is returned when |pos| is
+// located exactly on word boundary.
+static VisiblePosition nextWordBound(
+ const VisiblePosition& pos,
+ SearchDirection direction,
+ BoundAdjust wordBoundAdjust)
+{
+ bool nextBoundIfOnBound = wordBoundAdjust == BoundAdjust::NextBoundIfOnBound;
+ if (direction == SearchDirection::SearchForward) {
+ EWordSide wordSide = nextBoundIfOnBound ? RightWordIfOnBoundary : LeftWordIfOnBoundary;
+ return endOfWord(pos, wordSide);
+ }
+ EWordSide wordSide = nextBoundIfOnBound ? LeftWordIfOnBoundary : RightWordIfOnBoundary;
+ return startOfWord(pos, wordSide);
+}
+
GranularityStrategy::GranularityStrategy() { }
GranularityStrategy::~GranularityStrategy() { }
@@ -24,14 +67,19 @@ SelectionStrategy CharacterGranularityStrategy::GetType() const
void CharacterGranularityStrategy::Clear() { };
-VisibleSelection CharacterGranularityStrategy::updateExtent(const VisiblePosition& extentPosition, const VisibleSelection& selection)
+VisibleSelection CharacterGranularityStrategy::updateExtent(const IntPoint& extentPoint, LocalFrame* frame)
{
+ const VisiblePosition& extentPosition = visiblePositionForContentsPoint(extentPoint, frame);
+ const VisibleSelection& selection = frame->selection().selection();
+ if (selection.visibleBase() == extentPosition)
+ return selection;
return VisibleSelection(selection.visibleBase(), extentPosition);
}
DirectionGranularityStrategy::DirectionGranularityStrategy()
- : m_granularity(CharacterGranularity)
- , m_lastMoveShrunkSelection(false) { }
+ : m_state(StrategyState::Cleared)
+ , m_granularity(CharacterGranularity)
+ , m_offset(0) { }
DirectionGranularityStrategy::~DirectionGranularityStrategy() { }
@@ -42,86 +90,149 @@ SelectionStrategy DirectionGranularityStrategy::GetType() const
void DirectionGranularityStrategy::Clear()
{
+ m_state = StrategyState::Cleared;
m_granularity = CharacterGranularity;
- m_lastMoveShrunkSelection = false;
+ m_offset = 0;
+ m_diffExtentPointFromExtentPosition = IntSize();
}
-VisiblePosition DirectionGranularityStrategy::nextWordBound(
- const VisiblePosition& pos,
- SearchDirection direction,
- BoundAdjust wordBoundAdjust)
+VisibleSelection DirectionGranularityStrategy::updateExtent(const IntPoint& extentPoint, LocalFrame* frame)
{
- bool nextBoundIfOnBound = wordBoundAdjust == BoundAdjust::NextBoundIfOnBound;
- if (direction == SearchDirection::SearchForward) {
- EWordSide wordSide = nextBoundIfOnBound ? RightWordIfOnBoundary : LeftWordIfOnBoundary;
- return endOfWord(pos, wordSide);
+ const VisibleSelection& selection = frame->selection().selection();
+
+ if (m_state == StrategyState::Cleared)
+ m_state = StrategyState::Expanding;
+
+ VisiblePosition oldOffsetExtentPosition = selection.visibleExtent();
+ IntPoint oldExtentLocation = positionLocation(oldOffsetExtentPosition);
+
+ IntPoint oldOffsetExtentPoint = oldExtentLocation + m_diffExtentPointFromExtentPosition;
+ IntPoint oldExtentPoint = IntPoint(oldOffsetExtentPoint.x() - m_offset, oldOffsetExtentPoint.y());
+
+ // Apply the offset.
+ IntPoint newOffsetExtentPoint = extentPoint;
+ int dx = extentPoint.x() - oldExtentPoint.x();
+ if (m_offset != 0) {
+ if (m_offset > 0 && dx > 0)
+ m_offset = std::max(0, m_offset - dx);
+ else if (m_offset < 0 && dx < 0)
+ m_offset = std::min(0, m_offset - dx);
+ newOffsetExtentPoint.move(m_offset, 0);
}
- EWordSide wordSide = nextBoundIfOnBound ? LeftWordIfOnBoundary : RightWordIfOnBoundary;
- return startOfWord(pos, wordSide);
-}
-VisibleSelection DirectionGranularityStrategy::updateExtent(const VisiblePosition& extentPosition, const VisibleSelection& selection)
-{
- if (extentPosition == selection.visibleExtent())
- return selection;
+ VisiblePosition newOffsetExtentPosition = visiblePositionForContentsPoint(newOffsetExtentPoint, frame);
+ IntPoint newOffsetLocation = positionLocation(newOffsetExtentPosition);
- const VisiblePosition base = selection.visibleBase();
- const VisiblePosition oldExtentWithGranularity = selection.isBaseFirst() ? selection.visibleEnd() : selection.visibleStart();
-
- int extentBaseOrder = comparePositions(extentPosition, base);
- int oldExtentBaseOrder = comparePositions(oldExtentWithGranularity, base);
-
- bool extentBaseOrderSwitched = (extentBaseOrder > 0 && oldExtentBaseOrder < 0)
- || (extentBaseOrder < 0 && oldExtentBaseOrder > 0);
-
- // Determine the boundary of the 'current word', i.e. the boundary extending
- // beyond which should change the granularity to WordGranularity.
- // If the last move has shrunk the selection and is now exactly on the word
- // boundary - we need to take the next bound as the bound of the "current
- // word".
- VisiblePosition currentWordBoundary = nextWordBound(
- oldExtentWithGranularity,
- oldExtentBaseOrder > 0 ? SearchDirection::SearchForward : SearchDirection::SearchBackwards,
- m_lastMoveShrunkSelection ? BoundAdjust::NextBoundIfOnBound : BoundAdjust::CurrentPosIfOnBound);
-
- bool thisMoveShrunkSelection = (extentBaseOrder > 0 && comparePositions(extentPosition, selection.visibleExtent()) < 0)
- || (extentBaseOrder < 0 && comparePositions(extentPosition, selection.visibleExtent()) > 0);
- // If the extent-base order was switched, then the selection is now
- // expanding in a different direction than before. Therefore we need to
- // calculate the boundary of the 'current word' in this new direction in
- // order to be able to tell if the selection expanded beyond it.
- if (extentBaseOrderSwitched) {
- currentWordBoundary = nextWordBound(
- base,
- extentBaseOrder > 0 ? SearchDirection::SearchForward : SearchDirection::SearchBackwards,
- BoundAdjust::NextBoundIfOnBound);
+ // Reset the offset in case of a vertical change in the location (could be
+ // due to a line change or due to an unusual layout, e.g. rotated text).
+ bool verticalChange = newOffsetLocation.y() != oldExtentLocation.y();
+ if (verticalChange) {
+ m_offset = 0;
m_granularity = CharacterGranularity;
- // When the base/extent order switches it doesn't count as shrinking selection.
- thisMoveShrunkSelection = false;
+ newOffsetExtentPoint = extentPoint;
+ newOffsetExtentPosition = visiblePositionForContentsPoint(extentPoint, frame);
}
- bool expandedBeyondWordBoundary;
- if (extentBaseOrder > 0)
- expandedBeyondWordBoundary = comparePositions(extentPosition, currentWordBoundary) > 0;
- else
- expandedBeyondWordBoundary = comparePositions(extentPosition, currentWordBoundary) < 0;
- if (expandedBeyondWordBoundary) {
- m_granularity = WordGranularity;
- } else if (thisMoveShrunkSelection) {
- m_granularity = CharacterGranularity;
- m_lastMoveShrunkSelection = true;
+ const VisiblePosition base = selection.visibleBase();
+
+ // Do not allow empty selection.
+ if (newOffsetExtentPosition == base)
+ return selection;
+
+ // The direction granularity strategy, particularly the "offset" feature
+ // doesn't work with non-horizontal text (e.g. when the text is rotated).
+ // So revert to the behavior equivalent to the character granularity
+ // strategy if we detect that the text's baseline coordinate changed
+ // without a line change.
+ if (verticalChange && inSameLine(newOffsetExtentPosition, oldOffsetExtentPosition))
+ return VisibleSelection(selection.visibleBase(), newOffsetExtentPosition);
+
+ int oldExtentBaseOrder = selection.isBaseFirst() ? 1 : -1;
+
+ int newExtentBaseOrder;
+ bool thisMoveShrunkSelection;
+ if (newOffsetExtentPosition == oldOffsetExtentPosition) {
+ if (m_granularity == CharacterGranularity)
+ return selection;
+
+ // If we are in Word granularity, we cannot exit here, since we may pass
+ // the middle of the word without changing the position (in which case
+ // the selection needs to expand).
+ thisMoveShrunkSelection = false;
+ newExtentBaseOrder = oldExtentBaseOrder;
+ } else {
+ bool selectionExpanded = arePositionsInSpecifiedOrder(newOffsetExtentPosition, oldOffsetExtentPosition, oldExtentBaseOrder);
+ bool extentBaseOrderSwitched = selectionExpanded ? false : !arePositionsInSpecifiedOrder(newOffsetExtentPosition, base, oldExtentBaseOrder);
+ newExtentBaseOrder = extentBaseOrderSwitched ? -oldExtentBaseOrder : oldExtentBaseOrder;
+
+ // Determine the word boundary, i.e. the boundary extending beyond which
+ // should change the granularity to WordGranularity.
+ VisiblePosition wordBoundary;
+ if (extentBaseOrderSwitched) {
+ // Special case.
+ // If the extent-base order was switched, then the selection is now
+ // expanding in a different direction than before. Therefore we
+ // calculate the word boundary in this new direction and based on
+ // the |base| position.
+ wordBoundary = nextWordBound(
+ base,
+ newExtentBaseOrder > 0 ? SearchDirection::SearchForward : SearchDirection::SearchBackwards,
+ BoundAdjust::NextBoundIfOnBound);
+ m_granularity = CharacterGranularity;
+ } else {
+ // Calculate the word boundary based on |oldExtentWithGranularity|.
+ // If selection was shrunk in the last update and the extent is now
+ // exactly on the word boundary - we need to take the next bound as
+ // the bound of the current word.
+ wordBoundary = nextWordBound(
+ oldOffsetExtentPosition,
+ oldExtentBaseOrder > 0 ? SearchDirection::SearchForward : SearchDirection::SearchBackwards,
+ m_state == StrategyState::Shrinking ? BoundAdjust::NextBoundIfOnBound : BoundAdjust::CurrentPosIfOnBound);
+ }
+
+ bool expandedBeyondWordBoundary;
+ if (selectionExpanded)
+ expandedBeyondWordBoundary = arePositionsInSpecifiedOrder(newOffsetExtentPosition, wordBoundary, newExtentBaseOrder);
+ else if (extentBaseOrderSwitched)
+ expandedBeyondWordBoundary = arePositionsInSpecifiedOrder(newOffsetExtentPosition, wordBoundary, newExtentBaseOrder);
+ else
+ expandedBeyondWordBoundary = false;
+
+ // The selection is shrunk if the extent changes position to be closer to
+ // the base, and the extent/base order wasn't switched.
+ thisMoveShrunkSelection = !extentBaseOrderSwitched && !selectionExpanded;
+
+ if (expandedBeyondWordBoundary)
+ m_granularity = WordGranularity;
+ else if (thisMoveShrunkSelection)
+ m_granularity = CharacterGranularity;
}
- m_lastMoveShrunkSelection = thisMoveShrunkSelection;
- VisibleSelection newSelection = selection;
- newSelection.setExtent(extentPosition);
+ VisiblePosition newSelectionExtent = newOffsetExtentPosition;
if (m_granularity == WordGranularity) {
- if (extentBaseOrder > 0)
- newSelection.setEndRespectingGranularity(m_granularity, LeftWordIfOnBoundary);
- else
- newSelection.setStartRespectingGranularity(m_granularity, RightWordIfOnBoundary);
+ // Determine the bounds of the word where the extent is located.
+ // Set the selection extent to one of the two bounds depending on
+ // whether the extent is passed the middle of the word.
+ VisiblePosition boundBeforeExtent = nextWordBound(newOffsetExtentPosition, SearchDirection::SearchBackwards, BoundAdjust::CurrentPosIfOnBound);
+ VisiblePosition boundAfterExtent = nextWordBound(newOffsetExtentPosition, SearchDirection::SearchForward, BoundAdjust::CurrentPosIfOnBound);
+ int xMiddleBetweenBounds = (positionLocation(boundAfterExtent).x() + positionLocation(boundBeforeExtent).x()) / 2;
+ bool offsetExtentBeforeMiddle = newOffsetExtentPoint.x() < xMiddleBetweenBounds;
+ newSelectionExtent = offsetExtentBeforeMiddle ? boundBeforeExtent : boundAfterExtent;
+ // Update the offset if selection expanded in word granularity.
+ if (newSelectionExtent != selection.visibleExtent()
+ && ((newExtentBaseOrder > 0 && !offsetExtentBeforeMiddle) || (newExtentBaseOrder < 0 && offsetExtentBeforeMiddle))) {
+ m_offset = positionLocation(newSelectionExtent).x() - extentPoint.x();
+ }
}
+ // Only update the state if the selection actually changed as a result of
+ // this move.
+ if (newSelectionExtent != selection.visibleExtent())
+ m_state = thisMoveShrunkSelection ? StrategyState::Shrinking : StrategyState::Expanding;
+
+ m_diffExtentPointFromExtentPosition = extentPoint + IntSize(m_offset, 0) - positionLocation(newSelectionExtent);
+ VisibleSelection newSelection = selection;
+ newSelection.setExtent(newSelectionExtent);
return newSelection;
}
« no previous file with comments | « Source/core/editing/GranularityStrategy.h ('k') | Source/core/editing/GranularityStrategyTest.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698