blob: a338e55dc727a54ef3a9d880829e794a3b5fe452 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/paint/selection_bounds_recorder.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/layout/api/selection_state.h"
#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
namespace blink {
namespace {
// This represents a directional edge of a rect, starting at one corner and
// ending on another. Note that the 'left' and 'right' edges only have one
// variant because the edge always ends on the bottom. However in vertical
// writing modes, the edge end should follow the block direction, which can
// be flipped.
enum class RectEdge {
kTopLeftToBottomLeft,
kTopRightToBottomRight,
kTopLeftToTopRight,
kBottomLeftToBottomRight,
kTopRightToTopLeft,
kBottomRightToBottomLeft,
};
struct BoundEdges {
RectEdge start;
RectEdge end;
};
// Based on the given WritingMode and direction, return the pair of start and
// end edges that should be used to determe the PaintedSelectionBound start
// and end edges given a selection rectangle. For the simplest cases (i.e.
// LTR horizontal writing mode), the left edge is the start and the right edge
// would be the end. However, this flips for RTL, and vertical writing modes
// additionally complicated matters.
BoundEdges GetBoundEdges(WritingMode writing_mode, bool is_ltr) {
if (IsHorizontalWritingMode(writing_mode)) {
if (is_ltr)
return {RectEdge::kTopLeftToBottomLeft, RectEdge::kTopRightToBottomRight};
else
return {RectEdge::kTopRightToBottomRight, RectEdge::kTopLeftToBottomLeft};
} else if (IsFlippedBlocksWritingMode(writing_mode)) {
if (is_ltr)
return {RectEdge::kTopLeftToTopRight, RectEdge::kBottomRightToBottomLeft};
else
return {RectEdge::kBottomLeftToBottomRight, RectEdge::kTopRightToTopLeft};
} else {
if (is_ltr)
return {RectEdge::kTopRightToTopLeft, RectEdge::kBottomLeftToBottomRight};
else
return {RectEdge::kBottomRightToBottomLeft, RectEdge::kTopLeftToTopRight};
}
}
// Set the given bound's edge_start and edge_end, based on the provided
// selection rect and edge.
void SetBoundEdge(IntRect selection_rect,
RectEdge edge,
PaintedSelectionBound& bound) {
switch (edge) {
case RectEdge::kTopLeftToBottomLeft:
bound.edge_start = selection_rect.MinXMinYCorner();
bound.edge_end = selection_rect.MinXMaxYCorner();
return;
case RectEdge::kTopRightToBottomRight:
bound.edge_start = selection_rect.MaxXMinYCorner();
bound.edge_end = selection_rect.MaxXMaxYCorner();
return;
case RectEdge::kTopLeftToTopRight:
bound.edge_start = selection_rect.MinXMinYCorner();
bound.edge_end = selection_rect.MaxXMinYCorner();
return;
case RectEdge::kBottomLeftToBottomRight:
bound.edge_start = selection_rect.MinXMaxYCorner();
bound.edge_end = selection_rect.MaxXMaxYCorner();
return;
case RectEdge::kTopRightToTopLeft:
bound.edge_start = selection_rect.MaxXMinYCorner();
bound.edge_end = selection_rect.MinXMinYCorner();
return;
case RectEdge::kBottomRightToBottomLeft:
bound.edge_start = selection_rect.MaxXMaxYCorner();
bound.edge_end = selection_rect.MinXMaxYCorner();
return;
default:
NOTREACHED();
}
}
} // namespace
SelectionBoundsRecorder::SelectionBoundsRecorder(
SelectionState state,
PhysicalRect selection_rect,
PaintController& paint_controller,
TextDirection text_direction,
WritingMode writing_mode)
: state_(state),
selection_rect_(selection_rect),
paint_controller_(paint_controller),
text_direction_(text_direction),
writing_mode_(writing_mode) {
DCHECK(RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
}
SelectionBoundsRecorder::~SelectionBoundsRecorder() {
base::Optional<PaintedSelectionBound> start;
base::Optional<PaintedSelectionBound> end;
auto selection_rect = PixelSnappedIntRect(selection_rect_);
const bool is_ltr = IsLtr(text_direction_);
BoundEdges edges = GetBoundEdges(writing_mode_, is_ltr);
if (state_ == SelectionState::kStart ||
state_ == SelectionState::kStartAndEnd) {
start.emplace();
start->type = is_ltr ? gfx::SelectionBound::Type::LEFT
: gfx::SelectionBound::Type::RIGHT;
SetBoundEdge(selection_rect, edges.start, *start);
// TODO(crbug.com/1065049) Handle the case where selection within input
// text is clipped out.
start->hidden = false;
}
if (state_ == SelectionState::kStartAndEnd ||
state_ == SelectionState::kEnd) {
end.emplace();
end->type = is_ltr ? gfx::SelectionBound::Type::RIGHT
: gfx::SelectionBound::Type::LEFT;
SetBoundEdge(selection_rect, edges.end, *end);
end->hidden = false;
}
paint_controller_.RecordSelection(start, end);
}
bool SelectionBoundsRecorder::ShouldRecordSelection(
const FrameSelection& frame_selection,
SelectionState state) {
if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return false;
if (!frame_selection.IsHandleVisible() || frame_selection.IsHidden())
return false;
if (state == SelectionState::kInside || state == SelectionState::kNone)
return false;
return true;
}
} // namespace blink