blob: ff03e438c0533df42abb93c73b356fa02bf0f10a [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_VIEWS_CONTROLS_FOCUS_RING_H_
#define UI_VIEWS_CONTROLS_FOCUS_RING_H_
#include <memory>
#include "base/functional/callback.h"
#include "base/scoped_observation.h"
#include "ui/base/class_property.h"
#include "ui/base/metadata/metadata_types.h"
#include "ui/color/color_id.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/controls/focusable_border.h"
#include "ui/views/view.h"
#include "ui/views/view_observer.h"
#include "ui/views/views_export.h"
namespace views {
class HighlightPathGenerator;
// FocusRing is a View that is designed to act as an indicator of focus for its
// parent. It is a view that paints to a layer which extends beyond the bounds
// of its parent view.
// If MyView should show a rounded rectangular focus ring when it has focus and
// hide the ring when it loses focus, no other configuration is necessary. In
// other cases, it might be necessary to use the Set*() functions on FocusRing;
// these take care of repainting it when the state changes.
// TODO(tluk): FocusRing should not be a view but instead a new concept which
// only participates in view painting ( https://crbug.com/840796 ).
class VIEWS_EXPORT FocusRing : public View, public ViewObserver {
METADATA_HEADER(FocusRing, View)
public:
static constexpr float kDefaultCornerRadiusDp = 2.0f;
using ViewPredicate = base::RepeatingCallback<bool(const View* view)>;
// The default thickness and inset amount of focus ring halos. If you need
// the thickness of a specific focus ring, call halo_thickness() instead.
static constexpr float kDefaultHaloThickness = 2.0f;
// The default inset for the focus ring. Moves the ring slightly out from the
// edge of the host view, so that the halo doesn't significantly overlap the
// host view's contents. If you need a value for a specific focus ring, call
// halo_inset() instead.
static constexpr float kDefaultHaloInset = kDefaultHaloThickness * -0.5f;
// Creates a FocusRing and adds it to `host`.
static void Install(View* host);
// Gets the FocusRing, if present, from `host`.
static FocusRing* Get(View* host);
static const FocusRing* Get(const View* host);
// Removes the FocusRing, if present, from `host`.
static void Remove(View* host);
FocusRing(const FocusRing&) = delete;
FocusRing& operator=(const FocusRing&) = delete;
~FocusRing() override;
// Sets the HighlightPathGenerator to draw this FocusRing around.
// Note: This method should only be used if the focus ring needs to differ
// from the highlight shape used for InkDrops.
// Otherwise install a HighlightPathGenerator on the parent and FocusRing will
// use it as well.
void SetPathGenerator(std::unique_ptr<HighlightPathGenerator> generator);
// Sets whether the FocusRing should show an invalid state for the View it
// encloses.
void SetInvalid(bool invalid);
// Sets the predicate function used to tell when the parent has focus. The
// parent is passed into this predicate; it should return whether the parent
// should be treated as focused. This is useful when, for example, the parent
// wraps an inner view and the inner view is the one that actually receives
// focus, but the FocusRing sits on the parent instead of the inner view.
void SetHasFocusPredicate(const ViewPredicate& predicate);
std::optional<ui::ColorId> GetColorId() const;
void SetColorId(std::optional<ui::ColorId> color_id);
float GetHaloThickness() const;
float GetHaloInset() const;
void SetHaloThickness(float halo_thickness);
void SetHaloInset(float halo_inset);
// Explicitly disable using style of focus ring that is drawn with a 2dp gap
// between the focus ring and component.
void SetOutsetFocusRingDisabled(bool disable);
bool GetOutsetFocusRingDisabled() const;
bool ShouldPaintForTesting();
// View:
void Layout(PassKey) override;
void ViewHierarchyChanged(
const ViewHierarchyChangedDetails& details) override;
void OnPaint(gfx::Canvas* canvas) override;
void OnThemeChanged() override;
// ViewObserver:
void OnViewFocused(View* view) override;
void OnViewBlurred(View* view) override;
void OnViewLayoutInvalidated(View* view) override;
private:
FocusRing();
// Outset the input bounds if conditions are met.
void AdjustBounds(SkRect& rect) const;
void AdjustBounds(SkRRect& rect) const;
SkPath GetPath() const;
SkRRect GetRingRoundRect() const;
void RefreshLayer();
// Returns whether we should outset by `kFocusRingOutset` dp before drawing
// the focus ring.
bool ShouldSetOutsetFocusRing() const;
bool ShouldPaint();
// Translates the provided SkRect or SkRRect, which is in the parent's
// coordinate system, into this view's coordinate system, then insets it
// appropriately to produce the focus ring "halo" effect. If the supplied rect
// is an SkRect, it will have the default focus ring corner radius applied as
// well.
SkRRect RingRectFromPathRect(const SkRect& rect) const;
SkRRect RingRectFromPathRect(const SkRRect& rect) const;
// The path generator used to draw this focus ring.
std::unique_ptr<HighlightPathGenerator> path_generator_;
bool outset_focus_ring_disabled_ = false;
// Whether the enclosed View is in an invalid state, which controls whether
// the focus ring shows an invalid appearance (usually a different color).
bool invalid_ = false;
// Overriding color_id for the focus ring.
std::optional<ui::ColorId> color_id_;
// The predicate used to determine whether the parent has focus.
ViewPredicate has_focus_predicate_;
// The thickness of the focus ring halo, in DIP.
float halo_thickness_ = kDefaultHaloThickness;
// The adjustment from the visible border of the host view to render the
// focus ring.
//
// At -0.5 * halo_thickness_ (the default), the inner edge of the focus
// ring will align with the outer edge of the default inkdrop. For very thin
// focus rings, a zero value may provide better visual results.
float halo_inset_ = kDefaultHaloInset;
base::ScopedObservation<View, ViewObserver> view_observation_{this};
};
VIEWS_EXPORT SkPath
GetHighlightPath(const View* view,
float halo_thickness = FocusRing::kDefaultHaloThickness);
// Set this on the FocusRing host to have the FocusRing paint an outline around
// itself. This ensures that the FocusRing has sufficient contrast with its
// surroundings (this is used for prominent MdTextButtons because they are blue,
// while the background is light/dark, and the FocusRing doesn't contrast well
// with both the interior and exterior of the button). This may need some polish
// (such as blur?) in order to be expandable to all controls. For now it solves
// color contrast on prominent buttons which is an a11y issue. See
// https://crbug.com/1197631.
// TODO(pbos): Consider polishing this well enough that this can be
// unconditional. This may require rolling out `kCascadingBackgroundColor` to
// more surfaces to have an accurate background color.
VIEWS_EXPORT extern const ui::ClassProperty<bool>* const
kDrawFocusRingBackgroundOutline;
} // namespace views
#endif // UI_VIEWS_CONTROLS_FOCUS_RING_H_