| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/rounded_display/rounded_display_gutter.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/frame_sink/ui_resource.h" |
| #include "cc/paint/paint_flags.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "third_party/skia/include/core/SkPath.h" |
| #include "third_party/skia/include/core/SkScalar.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/geometry/vector2d.h" |
| |
| namespace ash { |
| namespace { |
| |
| using RoundedCornerPosition = RoundedDisplayGutter::RoundedCorner::Position; |
| using RoundedCorner = RoundedDisplayGutter::RoundedCorner; |
| |
| } // namespace |
| |
| RoundedCorner::RoundedCorner(Position position, |
| int radius, |
| const gfx::Point& origin) |
| : position_(position), |
| radius_(radius), |
| bounds_in_pixels_(gfx::Rect(origin, gfx::Size(radius, radius))) {} |
| |
| RoundedCorner& RoundedCorner::operator=(RoundedCorner&& other) = default; |
| RoundedCorner::RoundedCorner(RoundedCorner&& other) = default; |
| |
| RoundedCorner::~RoundedCorner() = default; |
| |
| bool RoundedDisplayGutter::RoundedCorner::DoesPaint() const { |
| return radius_ > 0; |
| } |
| |
| void RoundedDisplayGutter::RoundedCorner::Paint(gfx::Canvas* canvas) const { |
| if (!DoesPaint()) { |
| return; |
| } |
| |
| PaintCornerHelper(canvas); |
| } |
| |
| void RoundedDisplayGutter::RoundedCorner::PaintCornerHelper( |
| gfx::Canvas* canvas) const { |
| SkPath path; |
| SkScalar startAngle = 0.0, sweepAngle = 0.0; |
| SkScalar dx = 0.0, dy = 0.0; |
| int translate_dx = 0.0, translate_dy = 0.0; |
| |
| switch (position_) { |
| case RoundedCornerPosition::kUpperLeft: |
| startAngle = -90; |
| sweepAngle = -90; |
| dx = radius_; |
| dy = -radius_; |
| translate_dx = 0; |
| translate_dy = 0; |
| break; |
| case RoundedCornerPosition::kLowerLeft: |
| startAngle = 90; |
| sweepAngle = 90; |
| dx = radius_; |
| dy = radius_; |
| translate_dx = 0; |
| translate_dy = -radius_; |
| break; |
| case RoundedCornerPosition::kUpperRight: |
| startAngle = 0; |
| sweepAngle = -90; |
| dx = radius_; |
| dy = radius_; |
| translate_dx = -radius_; |
| translate_dy = 0; |
| break; |
| case RoundedCornerPosition::kLowerRight: |
| startAngle = 0; |
| sweepAngle = 90; |
| dx = radius_; |
| dy = -radius_; |
| translate_dx = -radius_; |
| translate_dy = -radius_; |
| break; |
| } |
| |
| const SkScalar oval_radius = radius_ * 2; |
| SkRect oval{0, 0, oval_radius, oval_radius}; |
| |
| path.addArc(oval, startAngle, sweepAngle); |
| |
| if (position_ == RoundedCornerPosition::kUpperLeft || |
| position_ == RoundedCornerPosition::kLowerLeft) { |
| path.rLineTo(0, dy); |
| path.rLineTo(dx, 0); |
| } |
| |
| if (position_ == RoundedCornerPosition::kUpperRight || |
| position_ == RoundedCornerPosition::kLowerRight) { |
| path.rLineTo(dx, 0); |
| path.rLineTo(0, dy); |
| } |
| |
| cc::PaintFlags flags; |
| flags.setStyle(cc::PaintFlags::Style::kFill_Style); |
| flags.setAntiAlias(true); |
| flags.setColor(SK_ColorBLACK); |
| |
| canvas->Save(); |
| canvas->Translate({translate_dx, translate_dy}); |
| canvas->DrawPath(path, flags); |
| canvas->Restore(); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // RoundedDisplayGutter: |
| |
| // static |
| std::unique_ptr<RoundedDisplayGutter> RoundedDisplayGutter::CreateGutter( |
| std::vector<RoundedCorner>&& corners, |
| bool is_overlay) { |
| return std::make_unique<RoundedDisplayGutter>(std::move(corners), is_overlay); |
| } |
| |
| RoundedDisplayGutter::RoundedDisplayGutter(std::vector<RoundedCorner>&& corners, |
| bool is_overlay) |
| : corners_(std::move(corners)), is_overlay_(is_overlay) { |
| // A gutter must paint at least one rounded corner and at most four corners. |
| DCHECK(corners_.size() > 0 && corners_.size() <= 4); |
| |
| // Since the corners of the gutter cannot be changed, both gutter bounds and |
| // ui_source_id do not change either. |
| bounds_in_pixels_ = CalculateGutterBounds(); |
| ui_source_id_ = CalculateUiSourceId(); |
| DCHECK(ui_source_id_ != kInvalidUiSourceId); |
| } |
| |
| RoundedDisplayGutter::~RoundedDisplayGutter() = default; |
| |
| UiSourceId RoundedDisplayGutter::ui_source_id() const { |
| return ui_source_id_; |
| } |
| |
| UiSourceId RoundedDisplayGutter::CalculateUiSourceId() const { |
| UiSourceId ui_source_id = kInvalidUiSourceId; |
| // Value of the position mask of the gutter will give a unique value for any |
| // combination of RoundedDisplayCorners. |
| for (const auto& corner : corners_) { |
| ui_source_id |= corner.position(); |
| } |
| |
| return ui_source_id; |
| } |
| |
| gfx::Rect RoundedDisplayGutter::CalculateGutterBounds() const { |
| gfx::Rect gutter_bounds; |
| |
| for (const auto& corner : corners_) { |
| gutter_bounds.Union(corner.bounds()); |
| } |
| |
| return gutter_bounds; |
| } |
| |
| const gfx::Rect& RoundedDisplayGutter::bounds() const { |
| return bounds_in_pixels_; |
| } |
| |
| void RoundedDisplayGutter::Paint(gfx::Canvas* canvas) const { |
| for (const auto& corner : corners_) { |
| canvas->Save(); |
| const gfx::Vector2d offset = |
| corner.bounds().OffsetFromOrigin() - bounds().OffsetFromOrigin(); |
| canvas->Translate(offset); |
| corner.Paint(canvas); |
| canvas->Restore(); |
| } |
| } |
| |
| } // namespace ash |