// Copyright 2016 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 "ui/display/win/scaling_util.h" #include #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/safe_integer_conversions.h" #include "ui/gfx/geometry/size.h" #include "ui/gfx/geometry/vector2d.h" #include "ui/gfx/range/range.h" namespace { // Represents the amount of rotation an object has about a coordinate plane. enum class CoordinateRotation { COORDINATE_ROTATE_0, COORDINATE_ROTATE_90, COORDINATE_ROTATE_180, COORDINATE_ROTATE_270, }; // Returns the CoordinateRotation necessary for |ref| and |other| so that |ref| // is positioned on top of |other|. CoordinateRotation ComputeCoordinateRotationRefTop(const gfx::Rect& ref, const gfx::Rect& other) { if (ref.bottom() <= other.y()) return CoordinateRotation::COORDINATE_ROTATE_0; if (other.right() <= ref.x()) return CoordinateRotation::COORDINATE_ROTATE_90; if (other.bottom() <= ref.y()) return CoordinateRotation::COORDINATE_ROTATE_180; return CoordinateRotation::COORDINATE_ROTATE_270; } gfx::Rect CoordinateRotateRectangle90(const gfx::Rect& rect) { return gfx::Rect(rect.y(), -rect.x() - rect.width(), rect.height(), rect.width()); } gfx::Rect CoordinateRotateRectangle180(const gfx::Rect& rect) { return gfx::Rect(-rect.x() - rect.width(), -rect.y() -rect.height(), rect.width(), rect.height()); } gfx::Rect CoordinateRotateRectangle270(const gfx::Rect& rect) { return gfx::Rect(-rect.y() - rect.height(), rect.x(), rect.height(), rect.width()); } gfx::Rect CoordinateRotateRect(const gfx::Rect& rect, CoordinateRotation rotation) { switch (rotation) { case CoordinateRotation::COORDINATE_ROTATE_90: return CoordinateRotateRectangle90(rect); case CoordinateRotation::COORDINATE_ROTATE_180: return CoordinateRotateRectangle180(rect); case CoordinateRotation::COORDINATE_ROTATE_270: return CoordinateRotateRectangle270(rect); default: return rect; } } bool InRange(int target, int lower_bound, int upper_bound) { return lower_bound <= target && target <= upper_bound; } // Scaled |unscaled_offset| to the same relative position on |unscaled_length| // based off of |unscaled_length|'s |scale_factor|. int ScaleOffset(int unscaled_length, float scale_factor, int unscaled_offset) { float scaled_length = static_cast(unscaled_length) / scale_factor; float percent = static_cast(unscaled_offset) / static_cast(unscaled_length); return gfx::ToFlooredInt(scaled_length * percent); } } // namespace namespace display { namespace win { bool DisplayInfosTouch(const DisplayInfo& a, const DisplayInfo& b) { const gfx::Rect& a_rect = a.screen_rect(); const gfx::Rect& b_rect = b.screen_rect(); int max_left = std::max(a_rect.x(), b_rect.x()); int max_top = std::max(a_rect.y(), b_rect.y()); int min_right = std::min(a_rect.right(), b_rect.right()); int min_bottom = std::min(a_rect.bottom(), b_rect.bottom()); return (max_left == min_right && a_rect.y() <= b_rect.bottom() && b_rect.y() <= a_rect.bottom()) || (max_top == min_bottom && a_rect.x() <= b_rect.right() && b_rect.x() <= a_rect.right()); } DisplayPlacement::Position CalculateDisplayPosition( const DisplayInfo& parent, const DisplayInfo& current) { const gfx::Rect& parent_rect = parent.screen_rect(); const gfx::Rect& current_rect = current.screen_rect(); int max_left = std::max(parent_rect.x(), current_rect.x()); int max_top = std::max(parent_rect.y(), current_rect.y()); int min_right = std::min(parent_rect.right(), current_rect.right()); int min_bottom = std::min(parent_rect.bottom(), current_rect.bottom()); if (max_left == min_right && max_top == min_bottom) { // Corner touching. if (parent_rect.bottom() == max_top) return DisplayPlacement::Position::BOTTOM; if (parent_rect.x() == max_left) return DisplayPlacement::Position::LEFT; return DisplayPlacement::Position::TOP; } if (max_left == min_right && parent_rect.y() <= current_rect.bottom() && current_rect.y() <= parent_rect.bottom()) { // Vertical edge touching. return parent_rect.x() == max_left ? DisplayPlacement::Position::LEFT : DisplayPlacement::Position::RIGHT; } if (max_top == min_bottom && parent_rect.x() <= current_rect.right() && current_rect.x() <= parent_rect.right()) { // Horizontal edge touching. return parent_rect.y() == max_top ? DisplayPlacement::Position::TOP : DisplayPlacement::Position::BOTTOM; } NOTREACHED() << "CalculateDisplayPosition relies on touching DisplayInfos."; return DisplayPlacement::Position::RIGHT; } DisplayPlacement CalculateDisplayPlacement(const DisplayInfo& parent, const DisplayInfo& current) { DCHECK(DisplayInfosTouch(parent, current)) << "DisplayInfos must touch."; DisplayPlacement placement; placement.parent_display_id = parent.id(); placement.display_id = current.id(); placement.position = CalculateDisplayPosition(parent, current); int parent_begin = 0; int parent_end = 0; int current_begin = 0; int current_end = 0; switch (placement.position) { case DisplayPlacement::Position::TOP: case DisplayPlacement::Position::BOTTOM: parent_begin = parent.screen_rect().x(); parent_end = parent.screen_rect().right(); current_begin = current.screen_rect().x(); current_end = current.screen_rect().right(); break; case DisplayPlacement::Position::LEFT: case DisplayPlacement::Position::RIGHT: parent_begin = parent.screen_rect().y(); parent_end = parent.screen_rect().bottom(); current_begin = current.screen_rect().y(); current_end = current.screen_rect().bottom(); break; } // Since we're talking offsets, make everything relative to parent_begin. parent_end -= parent_begin; current_begin -= parent_begin; current_end -= parent_begin; parent_begin = 0; // There are a few ways lines can intersect: // End Aligned // CURRENT's offset is relative to the end (in our world, BOTTOM_RIGHT). // +-PARENT----------------+ // +-CURRENT------------+ // // Positioning based off of |current_begin|. // CURRENT's offset is simply a percentage of its position on PARENT. // +-PARENT----------------+ // +-CURRENT------------+ // // Positioning based off of |current_end|. // CURRENT's offset is dependent on the percentage of its end position on // PARENT. // +-PARENT----------------+ // +-CURRENT------------+ // // Positioning based off of |parent_begin| on current. // CURRENT's offset is dependent on the percentage of its end position on // PARENT. // +-PARENT----------------+ // +-CURRENT--------------------------+ if (parent_end == current_end) { // End aligned. placement.offset_reference = DisplayPlacement::OffsetReference::BOTTOM_RIGHT; placement.offset = 0; } else if (InRange(current_begin, parent_begin, parent_end)) { placement.offset = ScaleOffset(parent_end, parent.device_scale_factor(), current_begin); } else if (InRange(current_end, parent_begin, parent_end)) { placement.offset_reference = DisplayPlacement::OffsetReference::BOTTOM_RIGHT; placement.offset = ScaleOffset(parent_end, parent.device_scale_factor(), parent_end - current_end); } else { DCHECK(InRange(parent_begin, current_begin, current_end)); placement.offset = ScaleOffset(current_end - current_begin, current.device_scale_factor(), current_begin); } return placement; } // This function rotates the rectangles so that |ref| is always on top of // |rect|, allowing the function to concentrate on comparing |ref|'s bottom // corners and |rect|'s top corners when the rects don't overlap vertically. int64_t SquaredDistanceBetweenRects(const gfx::Rect& ref, const gfx::Rect& rect) { if (ref.Intersects(rect)) return 0; CoordinateRotation degrees = ComputeCoordinateRotationRefTop(ref, rect); gfx::Rect top_rect(CoordinateRotateRect(ref, degrees)); gfx::Rect bottom_rect(CoordinateRotateRect(rect, degrees)); if (bottom_rect.right() < top_rect.x()) return (bottom_rect.top_right() - top_rect.bottom_left()).LengthSquared(); else if (top_rect.right() < bottom_rect.x()) return (bottom_rect.origin() - top_rect.bottom_right()).LengthSquared(); int distance = bottom_rect.y() - top_rect.bottom(); return distance * distance; } } // namespace win } // namespace display