blob: 1dd47a36ef3a0070b3600d7eeb974248f6667220 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// @docImport 'dart:ui';
library;
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'constants.dart';
/// Applies an iOS-style focus border around its child when any of child focus nodes gain focus.
///
/// The shape of the focus halo does not automatically adapt to the child widget
/// it encloses. You are responsible for specifying a shape that correctly
/// matches the child's geometry by using the appropriate constructor, such as
/// [CupertinoFocusHalo.withRect] or [CupertinoFocusHalo.withRRect].
///
/// See also:
///
/// * <https://developer.apple.com/design/human-interface-guidelines/focus-and-selection/>
class CupertinoFocusHalo extends StatefulWidget {
/// Creates a rectangular [CupertinoFocusHalo] around the child.
///
/// For example, to highlight a rectangular section of the widget tree when any button inside that
/// section has focus, one could write:
///
/// ```dart
/// CupertinoFocusHalo.withRect(
/// child: Column(
/// children: <Widget>[
/// CupertinoButton(child: const Text('Child 1'), onPressed: () {}),
/// CupertinoButton(child: const Text('Child 2'), onPressed: () {}),
/// ],
/// ),
/// )
/// ```
const CupertinoFocusHalo.withRect({required this.child, super.key})
: _borderRadius = BorderRadius.zero,
_shapeBuilder = RoundedRectangleBorder.new;
/// Creates a rounded rectangular [CupertinoFocusHalo] around the child
///
/// For example, to highlight a rounded rectangular section of the widget tree when any button inside that
/// section has focus, one could write:
///
/// ```dart
/// CupertinoFocusHalo.withRRect(
/// borderRadius: BorderRadius.circular(10.0),
/// child: Column(
/// children: <Widget>[
/// CupertinoButton(child: const Text('Child 1'), onPressed: () {}),
/// CupertinoButton(child: const Text('Child 2'), onPressed: () {}),
/// ],
/// ),
/// )
/// ```
const CupertinoFocusHalo.withRRect({
required this.child,
required BorderRadiusGeometry borderRadius,
super.key,
}) : _borderRadius = borderRadius,
_shapeBuilder = RoundedRectangleBorder.new;
/// Creates a rounded superellipse-shaped [CupertinoFocusHalo] around the child
///
/// For example, to highlight a rounded superellipse-shaped section of the widget tree when any button inside that
/// section has focus, one could write:
///
/// ```dart
/// CupertinoFocusHalo.withRoundedSuperellipse(
/// borderRadius: BorderRadius.circular(10.0),
/// child: Column(
/// children: <Widget>[
/// CupertinoButton(child: const Text('Child 1'), onPressed: () {}),
/// CupertinoButton(child: const Text('Child 2'), onPressed: () {}),
/// ],
/// ),
/// )
/// ```
///
/// See also:
///
/// * [RSuperellipse] and [RoundedSuperellipseBorder] for more introduction on
/// the rounded superellipse shape.
const CupertinoFocusHalo.withRoundedSuperellipse({
required this.child,
required BorderRadiusGeometry borderRadius,
super.key,
}) : _borderRadius = borderRadius,
_shapeBuilder = RoundedSuperellipseBorder.new;
final BorderRadiusGeometry _borderRadius;
final ShapeBorder Function({BorderRadiusGeometry borderRadius, BorderSide side}) _shapeBuilder;
/// The child to draw the focused border around.
///
/// Since [CupertinoFocusHalo] can't request focus to itself, this [child] should
/// contain widget(s) that can request focus.
///
/// The child widget is responsible for its own visual shape, for example by
/// using an appropriate clipping.
final Widget child;
@override
State<CupertinoFocusHalo> createState() => _CupertinoFocusHaloState();
}
class _CupertinoFocusHaloState extends State<CupertinoFocusHalo> {
bool _childHasFocus = false;
Color get _effectiveFocusOutlineColor =>
HSLColor.fromColor(CupertinoColors.activeBlue.withOpacity(kCupertinoFocusColorOpacity))
.withLightness(kCupertinoFocusColorBrightness)
.withSaturation(kCupertinoFocusColorSaturation)
.toColor();
@override
Widget build(BuildContext context) {
return Focus(
canRequestFocus: false,
skipTraversal: true,
includeSemantics: false,
onFocusChange: (bool hasFocus) {
setState(() {
_childHasFocus = hasFocus;
});
},
child: DecoratedBox(
position: DecorationPosition.foreground,
decoration: ShapeDecoration(
shape: widget._shapeBuilder(
side: _childHasFocus
? BorderSide(color: _effectiveFocusOutlineColor, width: 3.5)
: BorderSide.none,
borderRadius: widget._borderRadius,
),
),
child: widget.child,
),
);
}
}