| // 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 |