| // 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/views/controls/focus_ring.h" |
| |
| #include "ui/gfx/canvas.h" |
| #include "ui/views/controls/focusable_border.h" |
| #include "ui/views/style/platform_style.h" |
| #include "ui/views/view_properties.h" |
| |
| namespace views { |
| |
| namespace { |
| |
| ui::NativeTheme::ColorId ColorIdForValidity(bool valid) { |
| return valid ? ui::NativeTheme::kColorId_FocusedBorderColor |
| : ui::NativeTheme::kColorId_AlertSeverityHigh; |
| } |
| |
| double GetCornerRadius() { |
| double thickness = PlatformStyle::kFocusHaloThickness / 2.f; |
| return FocusableBorder::kCornerRadiusDp + thickness; |
| } |
| |
| } // namespace |
| |
| const char FocusRing::kViewClassName[] = "FocusRing"; |
| |
| // static |
| std::unique_ptr<FocusRing> FocusRing::Install(View* parent) { |
| auto ring = base::WrapUnique<FocusRing>(new FocusRing(parent)); |
| ring->set_owned_by_client(); |
| parent->AddChildView(ring.get()); |
| parent->AddObserver(ring.get()); |
| ring->Layout(); |
| ring->SchedulePaint(); |
| return ring; |
| } |
| |
| // static |
| bool FocusRing::IsPathUseable(const SkPath& path) { |
| return !path.isEmpty() && (path.isRect(nullptr) || path.isOval(nullptr) || |
| path.isRRect(nullptr)); |
| } |
| |
| void FocusRing::SetPath(const SkPath& path) { |
| path_ = IsPathUseable(path) ? path : SkPath(); |
| SchedulePaint(); |
| } |
| |
| void FocusRing::SetInvalid(bool invalid) { |
| invalid_ = invalid; |
| SchedulePaint(); |
| } |
| |
| void FocusRing::SetHasFocusPredicate(const ViewPredicate& predicate) { |
| has_focus_predicate_ = predicate; |
| SchedulePaint(); |
| } |
| |
| void FocusRing::SetColor(base::Optional<SkColor> color) { |
| color_ = color; |
| SchedulePaint(); |
| } |
| |
| const char* FocusRing::GetClassName() const { |
| return kViewClassName; |
| } |
| |
| void FocusRing::Layout() { |
| // The focus ring handles its own sizing, which is simply to fill the parent |
| // and extend a little beyond its borders. |
| gfx::Rect focus_bounds = parent()->GetLocalBounds(); |
| focus_bounds.Inset(gfx::Insets(PlatformStyle::kFocusHaloInset)); |
| SetBoundsRect(focus_bounds); |
| } |
| |
| void FocusRing::OnPaint(gfx::Canvas* canvas) { |
| if (!has_focus_predicate_(parent())) |
| return; |
| |
| cc::PaintFlags paint; |
| paint.setAntiAlias(true); |
| paint.setColor(color_.value_or(SkColorSetA( |
| GetNativeTheme()->GetSystemColor(ColorIdForValidity(!invalid_)), 0x66))); |
| paint.setStyle(cc::PaintFlags::kStroke_Style); |
| paint.setStrokeWidth(PlatformStyle::kFocusHaloThickness); |
| |
| SkPath path = path_; |
| if (path.isEmpty()) |
| path = GetHighlightPath(parent()); |
| |
| DCHECK(IsPathUseable(path)); |
| SkRect bounds; |
| SkRRect rbounds; |
| if (path.isRect(&bounds)) { |
| canvas->sk_canvas()->drawRRect(RingRectFromPathRect(bounds), paint); |
| } else if (path.isOval(&bounds)) { |
| gfx::RectF rect = gfx::SkRectToRectF(bounds); |
| View::ConvertRectToTarget(view_, this, &rect); |
| canvas->sk_canvas()->drawRRect(SkRRect::MakeOval(gfx::RectFToSkRect(rect)), |
| paint); |
| } else if (path.isRRect(&rbounds)) { |
| canvas->sk_canvas()->drawRRect(RingRectFromPathRect(rbounds), paint); |
| } |
| } |
| |
| void FocusRing::OnViewFocused(View* view) { |
| SchedulePaint(); |
| } |
| |
| void FocusRing::OnViewBlurred(View* view) { |
| SchedulePaint(); |
| } |
| |
| FocusRing::FocusRing(View* parent) : view_(parent) { |
| // A layer is necessary to paint beyond the parent's bounds. |
| SetPaintToLayer(); |
| layer()->SetFillsBoundsOpaquely(false); |
| // Don't allow the view to process events. |
| set_can_process_events_within_subtree(false); |
| |
| has_focus_predicate_ = [](View* p) -> bool { return p->HasFocus(); }; |
| } |
| |
| FocusRing::~FocusRing() { |
| if (parent()) |
| parent()->RemoveObserver(this); |
| } |
| |
| SkRRect FocusRing::RingRectFromPathRect(const SkRect& rect) const { |
| const double corner_radius = GetCornerRadius(); |
| return RingRectFromPathRect( |
| SkRRect::MakeRectXY(rect, corner_radius, corner_radius)); |
| } |
| |
| SkRRect FocusRing::RingRectFromPathRect(const SkRRect& rrect) const { |
| double thickness = PlatformStyle::kFocusHaloThickness / 2.f; |
| gfx::RectF r = gfx::SkRectToRectF(rrect.rect()); |
| View::ConvertRectToTarget(view_, this, &r); |
| |
| SkRRect skr = |
| rrect.makeOffset(r.x() - rrect.rect().x(), r.y() - rrect.rect().y()); |
| |
| // The focus indicator should hug the normal border, when present (as in the |
| // case of text buttons). Since it's drawn outside the parent view, increase |
| // the rounding slightly by adding half the ring thickness. |
| skr.inset(PlatformStyle::kFocusHaloInset, PlatformStyle::kFocusHaloInset); |
| skr.inset(thickness, thickness); |
| |
| return skr; |
| } |
| |
| SkPath GetHighlightPath(const View* view) { |
| SkPath* highlight_path = view->GetProperty(kHighlightPathKey); |
| if (highlight_path) |
| return *highlight_path; |
| |
| const double corner_radius = GetCornerRadius(); |
| SkPath path; |
| path.addRRect(SkRRect::MakeRectXY(RectToSkRect(view->GetLocalBounds()), |
| corner_radius, corner_radius)); |
| return path; |
| } |
| |
| } // namespace views |