Skip to content

Instantly share code, notes, and snippets.

@CoderNamedHendrick
Created June 19, 2024 07:10
Show Gist options
  • Save CoderNamedHendrick/ba06199fc0738e72306f09fce34744dd to your computer and use it in GitHub Desktop.
Save CoderNamedHendrick/ba06199fc0738e72306f09fce34744dd to your computer and use it in GitHub Desktop.
Highlight widget overlay
mixin OverlayStateMixin<T extends StatefulWidget> on State<T> {
@override
void dispose() {
removeOverlay();
super.dispose();
}
@override
void didChangeDependencies() {
removeOverlay();
super.didChangeDependencies();
}
bool get isOverlayShown => _overlayEntry != null;
void toggleOverlay({
required GlobalKey onboardItemKey,
required Widget overlayInformationChild,
}) =>
isOverlayShown
? removeOverlay()
: _insertOverlay(onboardItemKey, overlayInformationChild);
OverlayEntry? _overlayEntry;
void removeOverlay() {
_overlayEntry?.remove();
_overlayEntry = null;
}
Widget _dismissibleOverlay(
GlobalKey onboardItemKey,
Widget overlayInformationChild,
) {
final renderBox =
onboardItemKey.currentContext?.findRenderObject() as RenderBox;
final offset = renderBox.localToGlobal(Offset.zero);
final boxDecoration = (onboardItemKey.currentContext!.widget as Container)
.decoration as BoxDecoration;
final borderRadius = boxDecoration.borderRadius as BorderRadius;
return Material(
color: Colors.transparent,
child: Stack(
alignment: AlignmentDirectional.topStart,
children: [
Positioned.fill(
child: ClipPath(
clipper: _WidgetFocusClipper(
offset: offset,
widgetSize: renderBox.size,
widgetBorderRadius: borderRadius.topLeft,
),
child: ColoredBox(
color: Colors.black.withOpacity(0.7),
child: GestureDetector(
onTap: removeOverlay,
),
),
),
),
Positioned.fill(
top: offset.dy + renderBox.size.height + 8,
left: offset.dx,
child: OverlayInformation(
widgetAtTop: true,
child: overlayInformationChild,
),
),
],
),
);
}
void _insertOverlay(
GlobalKey onboardItemKey,
Widget overlayInformationChild,
) {
_overlayEntry = OverlayEntry(
builder: (_) => _dismissibleOverlay(
onboardItemKey,
overlayInformationChild,
),
);
Overlay.of(context).insert(_overlayEntry!);
}
}
class OverlayInformation extends StatelessWidget {
const OverlayInformation({
super.key,
required this.child,
this.widgetAtTop = true,
});
final Widget child;
final bool widgetAtTop;
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: _ChatIcon(isBottom: !widgetAtTop, child: child),
),
const SizedBox(width: 80),
],
);
}
}
class _ChatIcon extends StatelessWidget {
const _ChatIcon({
required this.child,
this.isBottom = false,
});
final Widget child;
final bool isBottom;
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
top: isBottom ? null : 0,
bottom: isBottom ? 0 : null,
left: 40,
child: RotatedBox(
quarterTurns: isBottom ? 2 : 0,
child: const CustomPaint(
painter: _RoundedTriangle(Colors.white),
size: Size(48, 50),
),
),
),
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isBottom) const SizedBox(height: 20),
Container(
padding: const EdgeInsets.all(18),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(8)),
),
alignment: Alignment.center,
child: child,
),
if (isBottom) const SizedBox(height: 20),
],
),
],
);
}
}
class _RoundedTriangle extends CustomPainter {
final Color color;
const _RoundedTriangle([this.color = Colors.red]);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..strokeJoin = StrokeJoin.round
..strokeWidth = 5
..strokeCap = StrokeCap.round
// ..style = PaintingStyle.stroke
..color = color;
final path = Path()
..moveTo(0, size.height - 20)
..quadraticBezierTo(
//control points
-10, size.height,
// endpoint
20, size.height,
)
..lineTo(size.width - 20, size.height)
..quadraticBezierTo(
// control points
size.width + 10, size.height,
// endpoints
size.width, size.height - 20,
)
..lineTo(size.width / 2 + 20, 20)
..quadraticBezierTo(
// control points
size.width / 2, -10,
// endpoints
size.width / 2 - 20, 20,
)
..close();
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
class _WidgetFocusClipper extends CustomClipper<Path> {
final Offset offset;
final Size widgetSize;
final Radius widgetBorderRadius;
const _WidgetFocusClipper({
required this.offset,
required this.widgetSize,
this.widgetBorderRadius = const Radius.circular(4.0),
});
@override
Path getClip(Size size) {
return Path()
..moveTo(0, 0)
..lineTo(0, size.height)
..lineTo(size.width, size.height)
..lineTo(size.width, 0)
..close()
..moveTo(offset.dx, offset.dy)
..addRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(
offset.dx - 5,
offset.dy - 5,
widgetSize.width + 10,
widgetSize.height + 10,
),
widgetBorderRadius + const Radius.circular(5),
),
);
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment